//========= Copyright Valve Corporation, 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_weaponbase.h"
#include "rope.h"
#include "rope_shared.h"
#include "bone_setup.h"
#include "ndebugoverlay.h"
#include "rope_helpers.h"
#include "IEffects.h"
#include "vstdlib/random.h"
#include "tier1/strtools.h"
#include "basegrenade_shared.h"
#include "tf_gamerules.h"
#include "engine/IEngineSound.h"
#include "tf_shareddefs.h"
#include "vguiscreen.h"
#include "hierarchy.h"
#include "func_no_build.h"
#include "func_respawnroom.h"
#include <KeyValues.h>
#include "ihasbuildpoints.h"
#include "utldict.h"
#include "filesystem.h"
#include "npcevent.h"
#include "tf_shareddefs.h"
#include "animation.h"
#include "effect_dispatch_data.h"
#include "te_effect_dispatch.h"
#include "tf_gamestats.h"
#include "tf_ammo_pack.h"
#include "tf_obj_sapper.h"
#include "particle_parse.h"
#include "tf_fx.h"
#include "trains.h"
#include "serverbenchmark_base.h"
#include "tf_weapon_wrench.h"
#include "tf_weapon_grenade_pipebomb.h"
#include "tf_weapon_builder.h"

#include "player_vs_environment/tf_population_manager.h"

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

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

#define ROPE_HANG_DIST	150
#define UPGRADE_LEVEL_HEALTH_MULTIPLIER 1.2f


ConVar tf_obj_gib_velocity_min( "tf_obj_gib_velocity_min", "100", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
ConVar tf_obj_gib_velocity_max( "tf_obj_gib_velocity_max", "450", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
ConVar tf_obj_gib_maxspeed( "tf_obj_gib_maxspeed", "800", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
ConVar tf_obj_upgrade_per_hit( "tf_obj_upgrade_per_hit", "25", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );

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

ConVar tf_obj_damage_tank_achievement_amount( "tf_obj_damage_tank_achievement_amount", "2000", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );

extern short g_sModelIndexFireball;
extern ConVar tf_cheapobjects;

// 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 PLASMA_DISABLE_TIME					4

IMPLEMENT_AUTO_LIST( IBaseObjectAutoList );

BEGIN_DATADESC( CBaseObject )
	// keys 
	DEFINE_KEYFIELD_NOT_SAVED( m_SolidToPlayers,		FIELD_INTEGER, "SolidToPlayer" ),
	DEFINE_KEYFIELD( m_nDefaultUpgradeLevel, FIELD_INTEGER, "defaultupgrade" ),

	DEFINE_THINKFUNC( UpgradeThink ),

	// Inputs
	DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ),
	DEFINE_INPUTFUNC( FIELD_INTEGER, "AddHealth", InputAddHealth ),
	DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveHealth", InputRemoveHealth ),
	DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSolidToPlayer", InputSetSolidToPlayer ),
	DEFINE_INPUTFUNC( FIELD_STRING,  "SetBuilder", InputSetBuilder ),
	DEFINE_INPUTFUNC( FIELD_VOID,  "Show", InputShow ),
	DEFINE_INPUTFUNC( FIELD_VOID,  "Hide", InputHide ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),

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


IMPLEMENT_SERVERCLASS_ST(CBaseObject, DT_BaseObject)
	SendPropInt(SENDINFO(m_iHealth), -1, SPROP_VARINT ),
	SendPropInt(SENDINFO(m_iMaxHealth), -1, SPROP_VARINT ),
	SendPropBool(SENDINFO(m_bHasSapper) ),
	SendPropInt(SENDINFO(m_iObjectType), Q_log2( OBJ_LAST ) + 1, SPROP_UNSIGNED ),
	SendPropBool(SENDINFO(m_bBuilding) ),
	SendPropBool(SENDINFO(m_bPlacing) ),
	SendPropBool(SENDINFO(m_bCarried) ),
	SendPropBool(SENDINFO(m_bCarryDeploy) ),
	SendPropBool(SENDINFO(m_bMiniBuilding) ),
	SendPropFloat(SENDINFO(m_flPercentageConstructed), 8, 0, 0.0, 1.0f ),
	SendPropInt(SENDINFO(m_fObjectFlags), OF_BIT_COUNT, SPROP_UNSIGNED ),
	SendPropEHandle(SENDINFO(m_hBuiltOnEntity)),
	SendPropBool( SENDINFO( m_bDisabled ) ),
	SendPropEHandle( SENDINFO( m_hBuilder ) ),
	SendPropVector( SENDINFO( m_vecBuildMaxs ), -1, SPROP_COORD ),
	SendPropVector( SENDINFO( m_vecBuildMins ), -1, SPROP_COORD ),
	SendPropInt( SENDINFO( m_iDesiredBuildRotations ), 2, SPROP_UNSIGNED ),
	SendPropBool( SENDINFO( m_bServerOverridePlacement ) ),
	SendPropInt( SENDINFO(m_iUpgradeLevel), 3 ),
	SendPropInt( SENDINFO(m_iUpgradeMetal), 10 ),
	SendPropInt( SENDINFO(m_iUpgradeMetalRequired), 10 ),
	SendPropInt( SENDINFO(m_iHighestUpgradeLevel), 3 ),
	SendPropInt( SENDINFO(m_iObjectMode), 2, SPROP_UNSIGNED ),
	SendPropBool( SENDINFO( m_bDisposableBuilding ) ),
	SendPropBool( SENDINFO( m_bWasMapPlaced ) ),
	SendPropBool( SENDINFO( m_bPlasmaDisable ) ),
END_SEND_TABLE();


bool PlayerIndexLessFunc( const int &lhs, const int &rhs )	
{ 
	return lhs < rhs; 
}

// 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 int ShouldTransmit( const CCheckTransmitInfo *pInfo, int nPrevShouldTransmitResult )
	{
		// Don't transmit the rope if it's not even visible.
		if ( !nPrevShouldTransmitResult )
			return FL_EDICT_DONTSEND;

		// 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 nPrevShouldTransmitResult;
		}

		// Give control to whichever object is being placed.
		if ( m_hObj1 && m_hObj1->IsPlacing() )
			return m_hObj1->ShouldTransmit( pInfo );
		
		else if ( m_hObj2 && m_hObj2->IsPlacing() )
			return m_hObj2->ShouldTransmit( pInfo );
		
		else
			return FL_EDICT_ALWAYS;
	}


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


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CBaseObject::CBaseObject()
{
	m_iHealth = m_iMaxHealth = m_flHealth = 0;
	m_flPercentageConstructed = 0;
	m_bPlacing = false;
	m_bBuilding = false;
	m_Activity = ACT_INVALID;
	m_bDisabled = false;
	m_SolidToPlayers = SOLID_TO_PLAYER_USE_DEFAULT;
	m_bPlacementOK = false;
	m_aGibs.Purge();
	m_iHighestUpgradeLevel = 1;
	m_bCarryDeploy = false;
	m_flCarryDeployTime = 0;
	m_iHealthOnPickup = 0;
	m_iLifetimeDamage = 0;
	m_bCannotDie = false;
	m_bMiniBuilding = false;
	m_flPlasmaDisableTime = 0;
	m_bPlasmaDisable = false;

	m_bDisposableBuilding = false;

	m_vecBuildForward = vec3_origin;
	m_flBuildDistance = 0.0f;

	m_bForceQuickBuild = false;
}

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

	// check for sapper crits
	CObjectSapper *pSapper = GetSapper();
	if ( pSapper )
	{
		// give an assist to the sapper's owner
		CTFPlayer *pSapperOwner = pSapper->GetOwner();
		if ( pSapperOwner )
		{
			pSapperOwner->m_Shared.IncrementRevengeCrits();
		}
	}

	DestroyObject();

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

	DetachObjectFromObject();

	// 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: 
//-----------------------------------------------------------------------------
int CBaseObject::UpdateTransmitState()
{
	return SetTransmitState( FL_EDICT_FULLCHECK );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CBaseObject::ShouldTransmit( const CCheckTransmitInfo *pInfo )
{
	// Always transmit to owner
	if ( GetBuilder() && pInfo->m_pClientEnt == GetBuilder()->edict() )
		return FL_EDICT_ALWAYS;

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

	if ( pInfo->m_pClientEnt )
	{
		CBaseEntity *pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt );
		if ( pRecipientEntity && pRecipientEntity->ShouldForceTransmitsForTeam( GetTeamNumber() ) )
			return FL_EDICT_ALWAYS;
	}

	return BaseClass::ShouldTransmit( pInfo );
}


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

	BaseClass::SetTransmit( pInfo, bAlways );

	// 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, bAlways );
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::Precache()
{
	PrecacheMaterial( SCREEN_OVERLAY_MATERIAL );

	PrecacheScriptSound( GetObjectInfo( ObjectType() )->m_pExplodeSound );
	PrecacheScriptSound( GetObjectInfo( ObjectType() )->m_pUpgradeSound );

	const char *pEffect = GetObjectInfo( ObjectType() )->m_pExplosionParticleEffect;

	if ( pEffect && pEffect[0] != '\0' )
	{
		PrecacheParticleSystem( pEffect );
	}

	PrecacheParticleSystem( "nutsnbolts_build" );
	PrecacheParticleSystem( "nutsnbolts_upgrade" );
	PrecacheParticleSystem( "nutsnbolts_repair" );

	PrecacheModel( "models/weapons/w_models/w_toolbox.mdl" );
}


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

	CollisionProp()->SetSurroundingBoundsType( USE_BEST_COLLISION_BOUNDS );
	SetSolidToPlayers( m_SolidToPlayers, true );

	m_bWasMapPlaced = false;
	m_bHasSapper = false;
	if ( HasSpawnFlags(SF_BASEOBJ_INVULN) )
	{
		m_takedamage = DAMAGE_NO;
	}
	else
	{
		m_takedamage = DAMAGE_YES;
	}

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

	m_iDesiredBuildRotations = 0;
	m_flCurrentBuildRotation = 0;

	if ( MustBeBuiltOnAttachmentPoint() )
	{
		AddEffects( EF_NODRAW );
	}

	// assume valid placement
	m_bServerOverridePlacement = true;

	m_iUpgradeLevel = 1;
	m_iUpgradeMetalRequired = GetUpgradeMetalRequired();

	if ( !IsCarried() )
	{
		FirstSpawn();
	}

	UpdateLastKnownArea();
}

//-----------------------------------------------------------------------------
// Purpose: Initialization that should only be done when the object is first created.
//-----------------------------------------------------------------------------
void CBaseObject::FirstSpawn()
{
	if ( !VPhysicsGetObject() )
		VPhysicsInitStatic();

	m_iUpgradeMetal = 0;
	m_iKills = 0;
	m_iAssists = 0;
	m_ConstructorList.SetLessFunc( PlayerIndexLessFunc );
	m_flHealth = m_iMaxHealth = m_iHealth;

	SetContextThink( &CBaseObject::BaseObjectThink, gpGlobals->curtime + 0.1, OBJ_BASE_THINK_CONTEXT );
}

//-----------------------------------------------------------------------------
// 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;
	const char *pOrgLL = "controlpanel%d_ll";
	const char *pOrgUR = "controlpanel%d_ur";
	const char *pAttachmentNameLL = pOrgLL;
	const char *pAttachmentNameUR = pOrgUR;
	if ( IsBuiltOnAttachment() )
	{
		pEntityToSpawnOn = dynamic_cast<CBaseAnimating*>((CBaseEntity*)m_hBuiltOnEntity.Get());
		if ( pEntityToSpawnOn )
		{
			char sBuildPointLL[64];
			char sBuildPointUR[64];
			Q_snprintf( sBuildPointLL, sizeof( sBuildPointLL ), "bp%d_controlpanel%%d_ll", m_iBuiltOnPoint );
			Q_snprintf( sBuildPointUR, sizeof( sBuildPointUR ), "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, sizeof( buf ), pAttachmentNameLL, nPanel );
		int nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
		if (nLLAttachmentIndex <= 0)
		{
			// Try and use my panels then
			pEntityToSpawnOn = this;
			Q_snprintf( buf, sizeof( buf ), pOrgLL, nPanel );
			nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
			if (nLLAttachmentIndex <= 0)
				return;
		}

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

		const char *pScreenName = NULL;
		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 );
		pScreen->SetTransparency( true );

		// for now, only input by the owning player
		pScreen->SetPlayerOwner( GetBuilder(), true );

		int nScreen = m_hScreens.AddToTail( );
		m_hScreens[nScreen].Set( pScreen );
	}
}

//-----------------------------------------------------------------------------
// Handle commands sent from vgui panels on the client 
//-----------------------------------------------------------------------------
bool CBaseObject::ClientCommand( CTFPlayer *pSender, const CCommand &args )
{
	//const char *pCmd = args[0];
	return false;
}

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

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

	DeterminePlaybackRate();

	if ( m_bPlasmaDisable )
	{
		if ( gpGlobals->curtime > (m_flPlasmaDisableTime ) )
		{
			m_bPlasmaDisable = false;
			UpdateDisabledState();
		}
	}

	// Do nothing while we're being placed
	if ( IsPlacing() )
	{
		if ( MustBeBuiltOnAttachmentPoint() )
		{
			UpdateAttachmentPlacement();	
			m_bServerOverridePlacement = true;
		}
		else
		{
			m_bServerOverridePlacement = IsPlacementPosValid();

			UpdateDesiredBuildRotation( BASE_OBJECT_THINK_DELAY );
		}

		return;
	}

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

	if ( IsUpgrading() )
	{
		UpgradeThink();
	}
	else
	{
		if ( GetReversesBuildingConstructionSpeed() > 0.0f )
		{
			DoReverseBuild();
		}
		else
		{
			if ( GetUpgradeLevel() < GetHighestUpgradeLevel() )
			{
				// Keep moving up levels until we reach the level we were at before.
				StartUpgrading();
			}
			else
			{
				m_bCarryDeploy = false;
			}
		}
	}
}

void CBaseObject::ResetPlacement( void )
{
	m_bPlacementOK = false;

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

	// teleport to builder's origin
	CTFPlayer *pPlayer = GetOwner();

	if ( pPlayer )
	{
		Teleport( &pPlayer->WorldSpaceCenter(), &GetLocalAngles(), NULL );
	}
}

bool CBaseObject::UpdateAttachmentPlacement( CBaseObject *pObjectOverride )
{
	// See if we should snap to a build position
	// finding one implies it is a valid position
	if ( FindSnapToBuildPos( pObjectOverride ) )
	{
		m_bPlacementOK = true;

		Teleport( &m_vecBuildOrigin, &GetLocalAngles(), NULL );
	}
	else
	{
		ResetPlacement();
	}

	return m_bPlacementOK;
}

//-----------------------------------------------------------------------------
// Purpose: Cheap check to see if we are in any server-defined No-build areas.
//-----------------------------------------------------------------------------
bool CBaseObject::EstimateValidBuildPos( void )
{
	// Make sure CalculatePlacementPos() has been called to setup the member variables used below

	CTFPlayer *pPlayer = GetOwner();

	if ( !pPlayer )
		return false;

	// Cannot build inside a nobuild brush
	if ( PointInNoBuild( m_vecBuildOrigin, this ) )
		return false;

	if ( PointInNoBuild( m_vecBuildCenterOfMass, this ) )
		return false;

	// If we're receiving trigger hurt damage, don't allow building here.
	if ( IsTakingTriggerHurtDamageAtPoint( m_vecBuildOrigin ) )
		return false;

	if ( IsTakingTriggerHurtDamageAtPoint( m_vecBuildCenterOfMass ) )
		return false;

	if ( PointInRespawnRoom( NULL, m_vecBuildOrigin ) && !g_pServerBenchmark->IsBenchmarkRunning() )
		return false;

	if ( PointInRespawnRoom( NULL, m_vecBuildCenterOfMass ) && !g_pServerBenchmark->IsBenchmarkRunning() )
		return false;

	Vector vecBuildFarEdge = m_vecBuildOrigin + m_vecBuildForward * ( m_flBuildDistance + 8.0f );
	if ( PointsCrossRespawnRoomVisualizer( pPlayer->WorldSpaceCenter(), vecBuildFarEdge ) )
		return false;

	return true;
}



//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::DeterminePlaybackRate( void )
{
	float flReverseBuildingConstructionSpeed = GetReversesBuildingConstructionSpeed();
	if ( flReverseBuildingConstructionSpeed == 0.0f )
	{
		flReverseBuildingConstructionSpeed = 1.0f;
	}
	else
	{
		flReverseBuildingConstructionSpeed *= -1.0f;
	}

	// If a sapper was added or removed part way through construction we need to invert the time to completion
	bool bAdjustCompleteTime = ( flReverseBuildingConstructionSpeed > 0.0f && GetPlaybackRate() < 0.0f ) || 
							   ( flReverseBuildingConstructionSpeed < 0.0f && GetPlaybackRate() >= 0.0f );

	if ( IsBuilding() )
	{
		// Default half rate, author build anim as if one player is building
		// ConstructionMultiplier already contains the reverse
		SetPlaybackRate( GetConstructionMultiplier() * 0.5 );	
	}
	else
	{
		SetPlaybackRate( 1.0 * flReverseBuildingConstructionSpeed );
	}

	if ( bAdjustCompleteTime )
	{
		float fRelativeCycle = ( ( flReverseBuildingConstructionSpeed > 0.0f ) ? ( 1.0f - GetCycle() ) : ( GetCycle() ) );

		float flUpgradeTime = ( ShouldQuickBuild() ? 0 : GetObjectInfo( ObjectType() )->m_flUpgradeDuration );
		flUpgradeTime /= ( ( flReverseBuildingConstructionSpeed < 0.0f ) ? ( flReverseBuildingConstructionSpeed * -1.0 ) : 1.0f );
		m_flUpgradeCompleteTime = gpGlobals->curtime + flUpgradeTime * fRelativeCycle;
		m_flTotalConstructionTime = m_flConstructionTimeLeft = GetTotalTime();
		
		float flNewConstructionTimeLeft = m_flConstructionTimeLeft * fRelativeCycle;
		m_flConstructionTimeLeft *= fRelativeCycle;

		m_flConstructionStartTime += m_flConstructionTimeLeft - flNewConstructionTimeLeft;
		m_flConstructionTimeLeft = flNewConstructionTimeLeft;
	}

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

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

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::SetBuilder( CTFPlayer *pBuilder )
{
	TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::SetBuilder builder %s\n", gpGlobals->curtime, 
		pBuilder ? pBuilder->GetPlayerName() : "NULL" ) );

	m_hBuilder = pBuilder;
}

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

//-----------------------------------------------------------------------------
// Purpose: Destroys the object, gives a chance to spawn an explosion
//-----------------------------------------------------------------------------
void CBaseObject::DetonateObject( void )
{
	// Blow us up.
	CTakeDamageInfo info( this, this, vec3_origin, GetAbsOrigin(), 0, DMG_GENERIC );
	Killed( info );
}

//-----------------------------------------------------------------------------
// 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() ) );

	// If we are carried, uncarry us before destruction.
	if ( IsCarried() && GetBuilder() )
	{
		DropCarriedObject( GetBuilder() );

		CTFWeaponBuilder *pBuilder = dynamic_cast<CTFWeaponBuilder*>( GetBuilder()->Weapon_OwnsThisID( TF_WEAPON_BUILDER ) );
		if ( pBuilder )
		{
			pBuilder->SwitchOwnersWeaponToLast();
		}
	}

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

	UTIL_Remove( this );

	DestroyScreens();
}

//-----------------------------------------------------------------------------
// Purpose: Remove any screens that are active on this object
//-----------------------------------------------------------------------------
void CBaseObject::DestroyScreens( void )
{
	// Kill the control panels
	int i;
	for ( i = m_hScreens.Count(); --i >= 0; )
	{
		DestroyVGuiScreen( m_hScreens[i].Get() );
	}
	m_hScreens.RemoveAll();
}

//-----------------------------------------------------------------------------
// Purpose: Get the total time it will take to build this object
//-----------------------------------------------------------------------------
float CBaseObject::GetTotalTime( void )
{
	float flBuildTime = GetObjectInfo( ObjectType() )->m_flBuildTime;

	CTFPlayer *pTFBuilder= GetBuilder();
	if ( pTFBuilder )
	{
		CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pTFBuilder, flBuildTime, mod_build_rate );
	}

	if ( tf_fastbuild.GetInt() )
	{
		flBuildTime = MIN( 2.f, flBuildTime );
	}

	// quick builds for engineers in Mann Vs Machine mode during setup time
	if ( TFGameRules()->IsQuickBuildTime() )
	{
		flBuildTime = MIN( 1.0f, flBuildTime );
	}

	return flBuildTime;
}

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

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

	// needed?
	m_nRenderMode = kRenderNormal;
	
	// Set my build size
	CollisionProp()->WorldSpaceAABB( &m_vecBuildMins.GetForModify(), &m_vecBuildMaxs.GetForModify() );
	m_vecBuildMins -= Vector( 4,4,0 );
	m_vecBuildMaxs += Vector( 4,4,0 );
	m_vecBuildMins -= GetAbsOrigin();
	m_vecBuildMaxs -= GetAbsOrigin();

	// Set the skin
	m_nSkin = ( GetTeamNumber() == TF_TEAM_RED ) ? 0 : 1;
}

//-----------------------------------------------------------------------------
// 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, CBasePlayer *pBuilder, float &flNearestPoint, Vector &vecNearestBuildPoint, bool bIgnoreChecks )
{
	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) )
			{
				if ( !bIgnoreChecks )
				{
					// ignore build points outside our view
					if ( !pBuilder->FInViewCone( vecBPOrigin ) )
						continue;

					// Do a trace to make sure we don't place attachments through things (players, world, etc...)
					Vector vecStart = pBuilder->EyePosition();
					trace_t trace;
					CTraceFilterNoNPCsOrPlayer ignorePlayersFilter( pBuilder, COLLISION_GROUP_NONE );
					UTIL_TraceLine( vecStart, vecBPOrigin, MASK_SOLID, &ignorePlayersFilter, &trace );
					if ( trace.m_pEnt != pEntity && trace.fraction != 1.0 )
						continue;
				}

				float flDist = (vecBPOrigin - pBuilder->GetAbsOrigin()).Length();

				// if this is closer, or is the first one in our view, check it out
				if ( bIgnoreChecks || ( 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: Find a buildpoint on the specified player
//-----------------------------------------------------------------------------
bool CBaseObject::FindBuildPointOnPlayer( CTFPlayer *pTFPlayer, CBasePlayer *pBuilder, float &flNearestPoint, Vector &vecNearestBuildPoint )
{
	bool bFoundPoint = false;

	if ( !pTFPlayer )
		return false;

	if ( pTFPlayer->m_Shared.InCond( TF_COND_SAPPED ) ) 
		return false;

	if ( pTFPlayer->m_Shared.IsInvulnerable() )
		return false;

	if ( pTFPlayer->m_Shared.InCond( TF_COND_PHASE ) )
		return false;

	Vector vecOrigin = pTFPlayer->GetAbsOrigin();
	QAngle vecAngles = pTFPlayer->GetAbsAngles();
	float flDist = ( vecOrigin - pBuilder->GetAbsOrigin() ).Length();
	if ( flDist <= 160.f )
	{
		flNearestPoint = flDist;
		vecNearestBuildPoint = vecOrigin;
		m_hBuiltOnEntity = (CBaseEntity *)pTFPlayer;

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

		bFoundPoint = true;
	}

	return bFoundPoint;
}

/*
class CTraceFilterIgnorePlayers : public CTraceFilterSimple
{
public:
	// It does have a base, but we'll never network anything below here..
	DECLARE_CLASS( CTraceFilterIgnorePlayers, CTraceFilterSimple );

	CTraceFilterIgnorePlayers( const IHandleEntity *passentity, int collisionGroup )
		: CTraceFilterSimple( passentity, collisionGroup )
	{
	}

	virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
	{
		CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity );

		if ( pEntity->IsPlayer() )
		{
			return false;
		}

		return true;
	}
};

//-----------------------------------------------------------------------------
// Purpose: Test around this build position to make sure it does not block a path
//-----------------------------------------------------------------------------
bool CBaseObject::TestPositionForPlayerBlock( Vector vecBuildOrigin, CBasePlayer *pPlayer )
{
	// find out the status of the 8 regions around this position
	int i;
	bool bNodeVisited[8];
	bool bNodeClear[8];

	// The first zone that is clear of obstructions
	int iFirstClear = -1;

	Vector vHalfPlayerDims = (VEC_HULL_MAX - VEC_HULL_MIN) * 0.5f;

	Vector vBuildDims = m_vecBuildMaxs - m_vecBuildMins;
	Vector vHalfBuildDims = vBuildDims * 0.5;

	 
	// the locations of the 8 test positions
	// boxes are adjacent to the object box and are at least as large as 
	// a player to ensure that a player can pass this location

	//	0  1  2
	//	7  X  3
	//	6  5  4

	static int iPositions[8][2] = 
	{
		{ -1, -1 },
		{ 0, -1 },
		{ 1, -1 },
		{ 1, 0 },
		{ 1, 1 },
		{ 0, 1 },
		{ -1, 1 },
		{ -1, 0 }
	};

	CTraceFilterIgnorePlayers traceFilter( this, COLLISION_GROUP_NONE );

	for ( i=0;i<8;i++ )
	{
		// mark them all as unvisited
		bNodeVisited[i] = false;

		Vector vecTest = vecBuildOrigin;		
		vecTest.x += ( iPositions[i][0] * ( vHalfBuildDims.x + vHalfPlayerDims.x ) );
		vecTest.y += ( iPositions[i][1] * ( vHalfBuildDims.y + vHalfPlayerDims.y ) );

		trace_t trace;
		UTIL_TraceHull( vecTest, vecTest, VEC_HULL_MIN, VEC_HULL_MAX, MASK_SOLID_BRUSHONLY, &traceFilter, &trace );

		bNodeClear[i] = ( trace.fraction == 1 && trace.allsolid != 1 && (trace.startsolid != 1) );

		// NDebugOverlay::Box( vecTest, VEC_HULL_MIN, VEC_HULL_MAX, bNodeClear[i] ? 0 : 255, bNodeClear[i] ? 255 : 0, 0, 20, 0.1 );

		// Store off the first clear location
		if ( iFirstClear < 0 && bNodeClear[i] )
		{
			iFirstClear = i;
		}
	}

	if ( iFirstClear < 0 )
	{
		// no clear space
		return false;
	}

	// visit all nodes that are adjacent
	RecursiveTestBuildSpace( iFirstClear, bNodeClear, bNodeVisited );

	// if we still have unvisited nodes, return false
	// unvisited nodes means that one or more nodes was unreachable from our start position
	// ie, two places the player might want to traverse but would not be able to if we built here
	for ( i=0;i<8;i++ )
	{
		if ( bNodeVisited[i] == false && bNodeClear[i] == true )
		{
			return false;
		}
	}

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Test around the build position, one quadrant at a time
//-----------------------------------------------------------------------------
void CBaseObject::RecursiveTestBuildSpace( int iNode, bool *bNodeClear, bool *bNodeVisited )
{
	// if the node is visited already
	if ( bNodeVisited[iNode] == true )
		return;

	// if the test node is blocked
	if ( bNodeClear[iNode] == false )
		return;

	bNodeVisited[iNode] = true;

	int iLeftNode = iNode - 1;
	if ( iLeftNode < 0 )
		iLeftNode = 7;

	RecursiveTestBuildSpace( iLeftNode, bNodeClear, bNodeVisited );

	int iRightNode = ( iNode + 1 ) % 8;

	RecursiveTestBuildSpace( iRightNode, bNodeClear, bNodeVisited );
}
*/

//-----------------------------------------------------------------------------
// Purpose: Move the placement model to the current position. Return false if it's an invalid position
//-----------------------------------------------------------------------------
bool CBaseObject::UpdatePlacement( void )
{
	if ( MustBeBuiltOnAttachmentPoint() )
	{
		return UpdateAttachmentPlacement();
	}
	
	// Finds bsp-valid place for building to be built
	// Checks for validity, nearby to other entities, in line of sight
	m_bPlacementOK = IsPlacementPosValid();

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

	return m_bPlacementOK;
}

//-----------------------------------------------------------------------------
// Purpose: See if we should be snapping to a build position
//-----------------------------------------------------------------------------
bool CBaseObject::FindSnapToBuildPos( CBaseObject *pObjectOverride )
{
	if ( !MustBeBuiltOnAttachmentPoint() )
		return false;

	CTFPlayer *pPlayer = GetOwner();

	if ( !pPlayer )
	{
		return false;
	}

	bool bSnappedToPoint = false;
	bool bShouldAttachToParent = false;

	Vector vecNearestBuildPoint = vec3_origin;

	// See if there are any nearby build positions to snap to
	float flNearestPoint = 9999;
	int i;

	bool bHostileAttachment = IsHostileUpgrade();
	int iMyTeam = GetTeamNumber();

	if ( !pObjectOverride )
	{
		int nTeamCount = TFTeamMgr()->GetTeamCount();
		for ( int iTeam = FIRST_GAME_TEAM; iTeam < nTeamCount; ++iTeam )
		{
			// Hostile attachments look for enemy objects only
			if ( bHostileAttachment ) 
			{
				if ( iTeam == iMyTeam )
				{
					continue;
				}
			}
			// Friendly attachments look for friendly objects only
			else if ( iTeam != iMyTeam )
			{
				continue;
			}

			CTFTeam *pTeam = ( CTFTeam * )GetGlobalTeam( iTeam );
			if ( !pTeam )
				continue;
			
			// See if we're allowed to build on Robots
			if ( TFGameRules() && TFGameRules()->GameModeUsesMiniBosses() && 
				 GetType() == OBJ_ATTACHMENT_SAPPER && !pPlayer->IsBot() )
			{
				CUtlVector< CTFPlayer * > playerVector;
				CollectPlayers( &playerVector, pPlayer->GetOpposingTFTeam()->GetTeamNumber(), COLLECT_ONLY_LIVING_PLAYERS );
				FOR_EACH_VEC( playerVector, i )
				{
					if ( !playerVector[i]->IsBot() )
						continue;

					if ( FindBuildPointOnPlayer( playerVector[i], pPlayer, flNearestPoint, vecNearestBuildPoint ) )
					{
						bSnappedToPoint = true;
						bShouldAttachToParent = true;
					}
				}
			}

			// look for nearby buildpoints on other objects
			for ( i = 0; i < pTeam->GetNumObjects(); i++ )
			{
				CBaseObject *pObject = pTeam->GetObject(i);
				Assert( pObject );
				if ( pObject && !pObject->IsPlacing() )
				{
					if ( FindNearestBuildPoint( pObject, pPlayer, flNearestPoint, vecNearestBuildPoint ) )
					{
						bSnappedToPoint = true;
						bShouldAttachToParent = true;
					}
				}
			}
		}	
	}
	else
	{
		if ( !pObjectOverride->IsPlacing() )
		{
			if ( FindNearestBuildPoint( pObjectOverride, pPlayer, flNearestPoint, vecNearestBuildPoint, true ) )
			{
				bSnappedToPoint = true;
				bShouldAttachToParent = true;
			}
		}
	}

	if ( !bSnappedToPoint )
	{
		AddEffects( EF_NODRAW );
	}
	else
	{
		RemoveEffects( EF_NODRAW );

		if ( bShouldAttachToParent )
		{
			AttachObjectToObject( m_hBuiltOnEntity.Get(), m_iBuiltOnPoint, vecNearestBuildPoint );
		}

		m_vecBuildOrigin = vecNearestBuildPoint;
	}

	return bSnappedToPoint;
}

//-----------------------------------------------------------------------------
// Purpose: Are we currently in a buildable position
//-----------------------------------------------------------------------------
bool CBaseObject::IsValidPlacement( void ) const
{
	return m_bPlacementOK;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
const char *CBaseObject::GetResponseRulesModifier( void )
{
	switch ( GetType() )
	{
	case OBJ_DISPENSER: return "objtype:dispenser"; break;
	case OBJ_TELEPORTER: return "objtype:teleporter"; break;
	case OBJ_SENTRYGUN: return "objtype:sentrygun"; break;
	case OBJ_ATTACHMENT_SAPPER: return "objtype:sapper"; break;
	default:
		break;
	}
	return NULL;
}

//-----------------------------------------------------------------------------
// 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() )
	{
		/*
		if ( ((CTFPlayer*)pBuilder)->IsPlayerClass( TF_CLASS_ENGINEER ) )
		{
			((CTFPlayer*)pBuilder)->HintMessage( HINT_ENGINEER_USE_WRENCH_ONOWN );
		}
		*/

		if ( !IsCarried() )
		{
			if ( !ShouldQuickBuild() )
			{
				int iAmountPlayerPaidForMe = ((CTFPlayer*)pBuilder)->StartedBuildingObject( m_iObjectType );
				if ( !iAmountPlayerPaidForMe )
				{
					// Player couldn't afford to pay for me, so abort
					ClientPrint( (CBasePlayer*)pBuilder, HUD_PRINTCENTER, "Not enough resources.\n" );
					StopPlacement();
					return false;
				}
			}

			((CTFPlayer*)pBuilder)->SpeakConceptIfAllowed( MP_CONCEPT_BUILDING_OBJECT, GetResponseRulesModifier() );
		}
		else
		{
			m_bCarried = false;
			m_bCarryDeploy = true;
			m_flCarryDeployTime = gpGlobals->curtime;
			SetActivity( ACT_OBJ_ASSEMBLING );

			((CTFPlayer*)pBuilder)->m_flCommentOnCarrying = 0.f;
			((CTFPlayer*)pBuilder)->SpeakConceptIfAllowed( MP_CONCEPT_REDEPLOY_BUILDING, GetResponseRulesModifier() );
		}
	}
	
	// Check to see if we need to add this to a hierarchy.  We can just do a simple ray trace from the center as
	// the placement code has guarenteed we are in a valid position.
	trace_t trace;
	UTIL_TraceHull( GetAbsOrigin() + Vector( 0.0f, 0.0f, 2.0f ), GetAbsOrigin() - Vector( 0.0f, 0.0f, 2.0f ), vec3_origin, vec3_origin, MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace );
	if ( trace.m_pEnt && trace.m_pEnt->IsBSPModel() )
	{
		CFuncTrackTrain *pTrain = dynamic_cast<CFuncTrackTrain*>( trace.m_pEnt );
		if ( pTrain )
		{
			SetParent( pTrain );
		}
	}

	// 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;
	if ( m_bCarryDeploy )
	{
		SetHealth( m_iHealthOnPickup );

		IGameEvent * event = gameeventmanager->CreateEvent( "player_dropobject" );
		if ( event )
		{
			CTFPlayer *pTFPlayer = ToTFPlayer( pBuilder );
			event->SetInt( "userid", pTFPlayer ? pTFPlayer->GetUserID() : 0 );
			event->SetInt( "object", GetType() );
			event->SetInt( "index", entindex() );	// object entity index

			gameeventmanager->FireEvent( event, true );	// don't send to clients
		}
	}
	else if ( IsMiniBuilding() )
	{
		int iHealth = GetMaxHealthForCurrentLevel();
		if ( !IsDisposableBuilding() )
		{
			iHealth /= 2.0f;
		}
		SetHealth( iHealth );
	}
	else
	{
		SetHealth( OBJECT_CONSTRUCTION_STARTINGHEALTH );
	}
	m_flPercentageConstructed = 0;

	m_nRenderMode = kRenderNormal; 
	RemoveSolidFlags( FSOLID_NOT_SOLID );

	// 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
	if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) )
	{
		SpawnControlPanels();
	}

	// Tell the object we've been built on that we exist
	if ( IsBuiltOnAttachment() && !m_hBuiltOnEntity->IsPlayer() )
	{
		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();
	m_flConstructionStartTime = gpGlobals->curtime;

	if ( pBuilder && pBuilder->IsPlayer() )
	{
		CTFPlayer *pTFBuilder = ToTFPlayer( pBuilder );
		pTFBuilder->FinishedObject( this );
		IGameEvent * event = gameeventmanager->CreateEvent( "player_builtobject" );
		if ( event )
		{
			event->SetInt( "userid", pTFBuilder->GetUserID() );
			event->SetInt( "object", ObjectType() );
			event->SetInt( "index", entindex() );	// object entity index
			gameeventmanager->FireEvent( event, true );	// don't send to clients
		}
	}

	m_vecBuildOrigin = GetAbsOrigin();

	int contents = UTIL_PointContents( m_vecBuildOrigin );
	if ( contents & MASK_WATER )
	{
		SetWaterLevel( 3 );
	}

	// instantly play the build anim
	DetermineAnimation();

	if ( IsMiniBuilding() && ( GetType() != OBJ_DISPENSER ) )
	{
		// Set the skin after placement mode.
		m_nSkin = ( GetTeamNumber() == TF_TEAM_RED ) ? 2 : 3;
	}

	if ( ShouldQuickBuild() )
	{
		DoQuickBuild();
	}

	return true;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CBaseObject::ShouldBeMiniBuilding( CTFPlayer* pPlayer )
{
	if ( !pPlayer )
		return false;

	CTFWrench* pWrench = dynamic_cast<CTFWrench*>( pPlayer->Weapon_OwnsThisID( TF_WEAPON_WRENCH ) );
	if ( !pWrench )
		return false;

	if ( TFGameRules()->GameModeUsesUpgrades() )
	{
		if ( pPlayer->GetNumObjects( OBJ_SENTRYGUN ) && pPlayer->CanBuild( OBJ_SENTRYGUN ) == CB_CAN_BUILD && !IsCarried() )
			return true;	
	}

	if ( !pWrench->IsPDQ() )
		return false;

	return true;
}

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

	m_bMiniBuilding = true;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseObject::MakeDisposableBuilding( CTFPlayer *pPlayer )
{
	m_bDisposableBuilding = true;
}

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

//-----------------------------------------------------------------------------
// 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;

	OnGoActive();

	// 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();

	if ( IsUsingReverseBuild() )
	{
		// if we don't have a sapper (but we should!) then set ourselves as the damager
		CObjectSapper *pSapper = GetSapper();
		CBaseEntity *pDamager = pSapper ? pSapper : this;
		int iCustomDamageType = pSapper ? TF_DMG_CUSTOM_SAPPER_RECORDER_DEATH : 0;

		CTakeDamageInfo info;
		info.SetInflictor( pDamager );
		info.SetAttacker( pDamager );
		info.SetDamageForce( vec3_origin );
		info.SetDamagePosition( GetAbsOrigin() );
		info.SetDamage( 0 );
		info.SetDamageType( DMG_CRUSH );
		info.SetDamageCustom( iCustomDamageType );
		Killed( info );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Objects store health in hacky ways
//-----------------------------------------------------------------------------
void CBaseObject::SetHealth( float flHealth )
{
	if ( m_bCarryDeploy && (flHealth>m_iHealthOnPickup) )
	{
		// If we are re-deploying after being carried we shouldn't gain more health than we had
		// on pickup until the deploy process is finished.
		flHealth = m_iHealthOnPickup;
	}

	bool changed = m_flHealth != flHealth;

	m_flHealth = flHealth;
	m_iHealth = ceil(m_flHealth);


	/*
	// If we a pose parameter, set the pose parameter to reflect our health
	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 );
	}
}

//-----------------------------------------------------------------------------
// 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, CDmgAccumulator *pAccumulator )
{
	// Prevent team damage here so blood doesn't appear
	if ( inputInfo.GetAttacker() )
	{
		if ( InSameTeam(inputInfo.GetAttacker()) )
		{
			// Pass Damage to enemy attachments
			int iNumObjects = GetNumObjectsOnMe();
			for ( int iPoint=iNumObjects-1;iPoint >= 0; --iPoint )
			{
				CBaseObject *pObject = GetBuildPointObject( iPoint );

				if ( pObject && pObject->IsHostileUpgrade() )
				{
					pObject->TraceAttack(inputInfo, vecDir, ptr, pAccumulator );
				}
			}
			return;
		}
	}

	SpawnBlood( ptr->endpos, vecDir, BloodColor(), inputInfo.GetDamage() );

	AddMultiDamage( inputInfo, 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;
	int idx;

	// Build the unique columns:
	for( 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.
			Q_strncpy(szRowName, g_DamageMap.GetElementName(idx), sizeof( szRowName ) );
			*strchr(szRowName,',') = '\0';

			char szRowNameComma[256];
			Q_snprintf( szRowNameComma, sizeof( szRowNameComma ), "%s,", szRowName );

			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];
					Q_strncpy( szRowNameCommaColumn, szRowNameComma, sizeof( szRowNameCommaColumn ) );					
					Q_strncat( szRowNameCommaColumn, g_UniqueColumns.GetElementName( ColumnIdx ), sizeof( szRowNameCommaColumn ), COPY_ALL_CHARACTERS );

					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: Return the first non-hostile object build on this object
//-----------------------------------------------------------------------------
CBaseEntity *CBaseObject::GetFirstFriendlyObjectOnMe( void )
{
	CBaseObject *pFirstObject = NULL;

	IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this);
	int iNumObjects = pBPInterface->GetNumObjectsOnMe();
	for ( int iPoint=0;iPoint<iNumObjects;iPoint++ )
	{
		CBaseObject *pObject = GetBuildPointObject( iPoint );

		if ( pObject && !pObject->IsHostileUpgrade() )
		{
			pFirstObject = pObject;
			break;
		}
	}

	return pFirstObject;
}

//-----------------------------------------------------------------------------
// Purpose: Pass the specified amount of damage through to any objects I have built on me
//-----------------------------------------------------------------------------
bool CBaseObject::PassDamageOntoChildren( const CTakeDamageInfo &info, float *flDamageLeftOver )
{
	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 = GetFirstFriendlyObjectOnMe();
	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() )
		{
			const 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 = GetFirstFriendlyObjectOnMe();
	}

	*flDamageLeftOver = flDamage;
	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CBaseObject::OnTakeDamage( const CTakeDamageInfo &info )
{
	if ( !IsAlive() )
		return info.GetDamage();

	if ( m_takedamage == DAMAGE_NO )
		return 0;

	if ( IsPlacing() )
		return 0;

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

		if ( TFGameRules() && TFGameRules()->IsTruceActive() )
		{
			// players cannot damage buildings while a truce is active
			if ( info.GetAttacker()->IsPlayer() && info.GetAttacker()->IsTruceValidForEnt() && ( ( info.GetAttacker()->GetTeamNumber() == TF_TEAM_RED ) || ( info.GetAttacker()->GetTeamNumber() == TF_TEAM_BLUE ) ) )
				return 0;
		}
	}

	m_AchievementData.AddDamagerToHistory( info.GetAttacker() );
	if ( info.GetAttacker()->IsPlayer() )
	{
		ToTFPlayer( info.GetAttacker() )->m_AchievementData.AddTargetToHistory( this );
	}

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

	float flDamage = info.GetDamage();

	// Buildings are resistant to plasma damage.
	if ( info.GetDamageCustom() == TF_DMG_CUSTOM_PLASMA )
	{
		flDamage *= 0.2f;
	}

	// Charged plasma damage disables buildings for a short time.
	if ( info.GetDamageCustom() == TF_DMG_CUSTOM_PLASMA_CHARGED )
	{
		flDamage *= 0.2f;
		m_flPlasmaDisableTime = gpGlobals->curtime + PLASMA_DISABLE_TIME;
		m_bPlasmaDisable = true;
		UpdateDisabledState();
	}

	// 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();
	}

	
	CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase *>(info.GetWeapon());
	if ( pWeapon )
	{
#ifdef STAGING_ONLY		
		// Attacker has building disabling properties
		float flDisablingAttack = 0.0f;
		CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flDisablingAttack, disable_buildings_on_hit );
		if ( flDisablingAttack )
		{
			// do not override if existing time is longer
			m_flPlasmaDisableTime = Max( gpGlobals->curtime + flDisablingAttack, m_flPlasmaDisableTime );
			m_bPlasmaDisable = true;
			UpdateDisabledState();
		}
#endif // STAGING_ONLY

		// Apply attributes that increase damage vs buildings
		CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flDamage, mult_dmg_vs_buildings );
		CTFPlayer *pAttacker = ToTFPlayer( info.GetAttacker() );
		if ( pAttacker )
		{
			pWeapon->ApplyOnHitAttributes( NULL, pAttacker, info );
		}
	}

	if ( TFGameRules()->IsPowerupMode() )
	{
		CTFPlayer *pAttacker = ToTFPlayer( info.GetAttacker() );
		if ( pAttacker )
		{
			if ( pAttacker->m_Shared.GetCarryingRuneType() == RUNE_STRENGTH || pAttacker->m_Shared.InCond( TF_COND_RUNE_IMBALANCE ) )
			{
				flDamage *= 2.f;
			}

			if ( pAttacker->m_Shared.GetCarryingRuneType() == RUNE_KNOCKOUT )
			{
				flDamage *= 4.f;
			}

			if ( pAttacker->m_Shared.GetCarryingRuneType() == RUNE_VAMPIRE )
			{
				int iModHealthOnHit = flDamage;

				if ( iModHealthOnHit > 0 )
				{
					pAttacker->TakeHealth( iModHealthOnHit, DMG_GENERIC );
				}
			}
		}
	}

	bool bFriendlyObjectsAttached = false;
	int iNumObjects = pBPInterface->GetNumObjectsOnMe();
	for ( int iPoint=0;iPoint<iNumObjects;iPoint++ )
	{
		CBaseObject *pObject = GetBuildPointObject( iPoint );

		if ( !pObject || pObject->IsHostileUpgrade() )
		{
			continue;
		}

		bFriendlyObjectsAttached = true;
		break;
	}

	// Don't look, Tom Bui!
	static struct
	{
		bool operator()( const float flHealth, const float flDamage ) const
		{
			return ( ( flHealth - flDamage ) < 1 );
		}
	} IsDamageFatal;

	// Only track actual damage - not overkill
	m_AchievementData.AddDamageEventToHistory( info.GetAttacker(), ( IsDamageFatal( m_flHealth, flDamage ) ) ? m_flHealth : flDamage );

	// if we cannot die
	if ( m_bCannotDie && IsDamageFatal( m_flHealth, flDamage ) )
	{
		flDamage = m_flHealth - 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 = ( bFriendlyObjectsAttached ) && IsDamageFatal( m_flHealth, flDamage );
	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 )
	{
		m_iLifetimeDamage += floor( MIN( flDamage, m_flHealth ) );
		if ( m_iLifetimeDamage > tf_obj_damage_tank_achievement_amount.GetInt() && GetBuilder() )
		{
			GetBuilder()->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_TANK_DAMAGE );
		}

		// Recheck our death possibility, because our objects may have all been blown off us by now
		bWillDieButCant = ( bFriendlyObjectsAttached ) && IsDamageFatal( m_flHealth, flDamage );
		if ( !bWillDieButCant )
		{
			// Reduce health
			SetHealth( m_flHealth - flDamage );
		}
	}

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

	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( info );

		// Tell our builder to speak about it
		if ( m_hBuilder )
		{
			m_hBuilder->SpeakConceptIfAllowed( MP_CONCEPT_LOST_OBJECT, GetResponseRulesModifier() );
		}
	}

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

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

	IGameEvent *event = gameeventmanager->CreateEvent( "npc_hurt" );
	if ( event )
	{
		event->SetInt( "entindex", entindex() );
		event->SetInt( "health", Max( 0, (int)GetHealth() ) );
		event->SetInt( "damageamount", flDamage );
		event->SetBool( "crit", ( info.GetDamageType() & DMG_CRITICAL ) ? true : false );

		CTFPlayer *pTFAttacker = ToTFPlayer( info.GetAttacker() );
		if ( pTFAttacker )
		{
			event->SetInt( "attacker_player", pTFAttacker->GetUserID() );

			if ( pTFAttacker->GetActiveTFWeapon() )
			{
				event->SetInt( "weaponid", pTFAttacker->GetActiveTFWeapon()->GetWeaponID() );
			}
			else
			{
				event->SetInt( "weaponid", 0 );
			}
		}
		else
		{
			// hurt by world
			event->SetInt( "attacker_player", 0 );
			event->SetInt( "weaponid", 0 );
		}

		gameeventmanager->FireEvent( event );
	}

	return flDamage;
}

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

	if ( IsBuilding() )
	{
		// Reduce the construction time by the correct amount for the health passed in
		float flConstructionTime = flHealth / ((GetMaxHealth() - OBJECT_CONSTRUCTION_STARTINGHEALTH) / m_flTotalConstructionTime);
		if ( flConstructionTime < 0.0f )
		{
			flConstructionTime *= -1.0f;
		}

		m_flConstructionTimeLeft = MAX( 0, m_flConstructionTimeLeft - flConstructionTime);
		m_flConstructionTimeLeft = clamp( m_flConstructionTimeLeft, 0.0f, m_flTotalConstructionTime );

		m_flPercentageConstructed = m_flConstructionTimeLeft / m_flTotalConstructionTime;

		if ( flHealth >= 0.0f )
		{
			// Only do this if we're not reversing construction
			m_flPercentageConstructed = 1.0f - m_flPercentageConstructed;
		}
		m_flPercentageConstructed = clamp( (float) m_flPercentageConstructed, 0.0f, 1.0f );

		// Increase health (unless it's a mini-building, which start at max health)
		// Minibuildings build health at a reduced rate
		// Staging_engy
		{
			SetHealth( MIN( GetMaxHealth(), m_flHealth + (IsMiniBuilding() ? (flHealth * 0.5f) : 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(), MAX( 1, m_flHealth + flHealth ) ) );

		m_OnRepaired.FireOutput( this, this);

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

	return false;
}

//----------------------------------------------------------------------------------------------------------------------------------------
void CBaseObject::OnConstructionHit( CTFPlayer *pPlayer, CTFWrench *pWrench, Vector hitLoc )
{
	// Get the player index
	int iPlayerIndex = pPlayer->entindex();

	// The time the repair is going to expire
	float flRepairExpireTime = gpGlobals->curtime + 1.0;

	// Update or Add the expire time to the list
	int index = m_ConstructorList.Find( iPlayerIndex );
	if ( index == m_ConstructorList.InvalidIndex() )
	{
		index = m_ConstructorList.Insert( iPlayerIndex );
		m_ConstructorList[index].flValue = pWrench->GetConstructionValue();
	}

	m_ConstructorList[index].flHitTime = flRepairExpireTime;

	// Play a construction hit effect.
	CPVSFilter filter( hitLoc );
	TE_TFParticleEffect( filter, 0.0f, "nutsnbolts_build", hitLoc, QAngle(0,0,0) );
}

//----------------------------------------------------------------------------------------------------------------------------------------
float CBaseObject::GetConstructionMultiplier( void )
{
	if ( IsUsingReverseBuild() )
		return -1.0f;

	float flMultiplier = 1.0;

	// expire all the old 
	int i = m_ConstructorList.LastInorder();
	while ( i != m_ConstructorList.InvalidIndex() )
	{
		int iThis = i;
		i = m_ConstructorList.PrevInorder( i );
		if ( m_ConstructorList[iThis].flHitTime < gpGlobals->curtime )
		{
			m_ConstructorList.RemoveAt( iThis );
		}
		else
		{
			// STAGING_ENGY
			// each Player adds a fixed amount of speed boost
			// Carry deploy hits add more
			flMultiplier += ( m_ConstructorList[iThis].flValue );
		}
	}

	// See if we have any attributes that want to modify our build rate
	CTFPlayer* pBuilder = GetOwner();
	if( pBuilder )
	{
		flMultiplier += pBuilder->GetObjectBuildSpeedMultiplier( ObjectType(), m_bCarryDeploy );
	}

	return flMultiplier;
}

//-----------------------------------------------------------------------------
// Purpose: Object is exploding because it was killed or detonate
//-----------------------------------------------------------------------------
void CBaseObject::Explode( void )
{
	const char *pExplodeSound = GetObjectInfo( ObjectType() )->m_pExplodeSound;

	if ( pExplodeSound && Q_strlen(pExplodeSound) > 0 )
	{
		EmitSound( pExplodeSound );
	}

	const char *pExplodeEffect = GetObjectInfo( ObjectType() )->m_pExplosionParticleEffect;
	if ( pExplodeEffect && pExplodeEffect[0] != '\0' )
	{
		// Send to everyone - we're inside prediction for the engy who hit this off, but we 
		// don't predict that the hit will kill this object.
		CDisablePredictionFiltering disabler;

		Vector origin = GetAbsOrigin();
		QAngle up(-90,0,0);

		CPVSFilter filter( origin );
		TE_TFParticleEffect( filter, 0.0f, pExplodeEffect, origin, up );
	}

	// create some delicious, metal filled gibs
	CreateObjectGibs();
}

void CBaseObject::CreateObjectGibs( void )
{
	if ( m_aGibs.Count() <= 0 )
	{
		return;
	}

	const CObjectInfo *pObjectInfo = GetObjectInfo( ObjectType() );

	// grant some percentage of the cost to build if number of metal to drop is not specified
	const float flMetalCostPercentage = 0.5f;
	const int nTotalMetal = pObjectInfo->m_iMetalToDropInGibs == 0 ? pObjectInfo->m_Cost * flMetalCostPercentage : pObjectInfo->m_iMetalToDropInGibs;

	
	int nMetalPerGib = nTotalMetal / m_aGibs.Count();
	int nLeftOver = nTotalMetal % m_aGibs.Count();

	if ( IsMiniBuilding() )
	{
		// STAGING_ENGY
		nMetalPerGib = 0;
		nLeftOver = 0;
	}

	int i;
	for ( i=0; i<m_aGibs.Count(); i++ )
	{
		// make sure we drop all metal include left over from int math
		CreateAmmoPack( m_aGibs[i].modelName, i == 0 ? nMetalPerGib + nLeftOver : nMetalPerGib );
	}
}

CTFAmmoPack* CBaseObject::CreateAmmoPack( const char *pchModel, int nMetal )
{
	CTFAmmoPack *pAmmoPack = CTFAmmoPack::Create( GetAbsOrigin(), GetAbsAngles(), this, pchModel );
	Assert( pAmmoPack );
	if ( pAmmoPack )
	{
		pAmmoPack->ActivateWhenAtRest();

		// Fill up the ammo pack.
		pAmmoPack->GiveAmmo( nMetal, TF_AMMO_METAL );

		// Calculate the initial impulse on the weapon.
		Vector vecImpulse( random->RandomFloat( -0.5, 0.5 ), random->RandomFloat( -0.5, 0.5 ), random->RandomFloat( 0.75, 1.25 ) );
		VectorNormalize( vecImpulse );
		vecImpulse *= random->RandomFloat( tf_obj_gib_velocity_min.GetFloat(), tf_obj_gib_velocity_max.GetFloat() );

		// Cap the impulse.
		float flSpeed = vecImpulse.Length();
		if ( flSpeed > tf_obj_gib_maxspeed.GetFloat() )
		{
			VectorScale( vecImpulse, tf_obj_gib_maxspeed.GetFloat() / flSpeed, vecImpulse );
		}

		if ( pAmmoPack->VPhysicsGetObject() )
		{
			// We can probably remove this when the mass on the weapons is correct!
			//pAmmoPack->VPhysicsGetObject()->SetMass( 25.0f );
			AngularImpulse angImpulse( 0, random->RandomFloat( 0, 100 ), 0 );
			pAmmoPack->VPhysicsGetObject()->SetVelocityInstantaneous( &vecImpulse, &angImpulse );
		}

		pAmmoPack->SetInitialVelocity( vecImpulse );

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

		// Give the ammo pack some health, so that trains can destroy it.
		pAmmoPack->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
		pAmmoPack->m_takedamage = DAMAGE_YES;		
		pAmmoPack->SetHealth( 900 );
		pAmmoPack->m_bObjGib = true;

		if ( IsMiniBuilding() )
		{
			pAmmoPack->SetModelScale( 0.6f );
		}
	}

	return pAmmoPack;
}

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

	// Find the killer & the scorer
	CBaseEntity *pInflictor = info.GetInflictor();
	CBaseEntity *pKiller = info.GetAttacker();
	CTFPlayer *pScorer = ToTFPlayer( TFGameRules()->GetDeathScorer( pKiller, pInflictor, this ) );
	CTFPlayer *pAssister = NULL;

	// If we are being carried, 

	// if this object has a sapper on it, and was not killed by the sapper (killed by damage other than crush, since sapper does crushing damage),
	// award an assist to the owner of the sapper since it probably contributed to destroying this object
	CObjectSapper *pSapper = GetSapper();
	if ( pSapper && !( DMG_CRUSH & info.GetDamageType() ) && !m_bPlasmaDisable )
	{
		// give an assist to the sapper's owner
		pAssister = pSapper->GetOwner();
		if ( pAssister )
		{
			CTF_GameStats.Event_AssistDestroyBuilding( pAssister, this );

			// Also increment the SapBuildings grind achievement
			pAssister->AwardAchievement( ACHIEVEMENT_TF_SPY_SAPPER_GRIND );
		}
	}
	else if ( pScorer )
	{
		// If a player is healing the scorer, give that player credit for the assist
		CTFPlayer *pHealer = ToTFPlayer( static_cast<CBaseEntity *>( pScorer->m_Shared.GetFirstHealer() ) );
		// Must be a medic to receive a healing assist, otherwise engineers get credit for assists from dispensers doing healing.
		// Also don't give an assist for healing if the inflictor was a sentry gun, otherwise medics healing engineers get assists for the engineer's sentry kills.
		if ( pHealer && ( pHealer->GetPlayerClass()->GetClassIndex() == TF_CLASS_MEDIC ) )
		{
			pAssister = pHealer;
		}
	}

	// Don't do anything if we were detonated or dismantled
	if ( pScorer && pInflictor != this )
	{
		IGameEvent * event = gameeventmanager->CreateEvent( "object_destroyed" );

		// Work out what killed the player, and send a message to all clients about it
		int iWeaponID;
		const char *killer_weapon_name = TFGameRules()->GetKillingWeaponName( info, NULL, &iWeaponID );
		const char *killer_weapon_log_name = killer_weapon_name;

		CTFPlayer *pTFPlayer = GetOwner();

		CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase * >( pScorer->Weapon_OwnsThisID( iWeaponID ) );
		if ( pWeapon )
		{
			CEconItemView *pItem = pWeapon->GetAttributeContainer()->GetItem();

			if ( pItem )
			{
				if ( pItem->GetStaticData()->GetIconClassname() )
				{
					killer_weapon_name = pItem->GetStaticData()->GetIconClassname();
				}

				if ( pItem->GetStaticData()->GetLogClassname() )
				{
					killer_weapon_log_name = pItem->GetStaticData()->GetLogClassname();
				}
			}
		}

		if ( event )
		{
			if ( pTFPlayer )
			{
				event->SetInt( "userid", pTFPlayer->GetUserID() );
			}
			if ( pAssister && ( pAssister != pScorer ) )
			{
				event->SetInt( "assister", pAssister->GetUserID() );
			}
			
			event->SetInt( "attacker", pScorer->GetUserID() );	// attacker
			event->SetString( "weapon", killer_weapon_name );
			event->SetString( "weapon_logclassname", killer_weapon_log_name );
			event->SetInt( "weaponid", iWeaponID );
			event->SetInt( "priority", 6 );		// HLTV event priority, not transmitted
			event->SetInt( "objecttype", GetType() );
			event->SetInt( "index", entindex() );	// object entity index
			event->SetBool( "was_building", m_bBuilding );

			gameeventmanager->FireEvent( event );
		}

		CTF_GameStats.Event_PlayerDestroyedBuilding( pScorer, this );
		pScorer->Event_KilledOther(this, info);

		// Also track stats for strange sappers.
		if ( pSapper )
		{
			CTFPlayer *pSapperOwner = pSapper->GetOwner();
			Assert( pSapperOwner );

			if ( pSapperOwner )
			{
				EconEntity_OnOwnerKillEaterEvent( dynamic_cast<CEconEntity *>( pSapperOwner->GetEntityForLoadoutSlot( LOADOUT_POSITION_BUILDING ) ),
												  pSapperOwner,
												  GetOwner(),
												  kKillEaterEvent_BuildingSapped );
			}
		}

		// Check for Demo achievement:
		// Kill an Engineer building that you can't see with a direct hit from a Grenade Launcher

		if ( pScorer && pScorer->IsPlayerClass( TF_CLASS_DEMOMAN) )
		{
			if ( pScorer->GetActiveTFWeapon() && ( pScorer->GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_GRENADELAUNCHER) )
			{
				if ( pInflictor && pInflictor->IsPlayer() == false )
				{
					CTFGrenadePipebombProjectile *pBaseGrenade = dynamic_cast< CTFGrenadePipebombProjectile* >( pInflictor );
					if ( pBaseGrenade && pBaseGrenade->m_bTouched == false )
					{
						if ( pScorer->FVisible( this ) == false )
						{
							pScorer->AwardAchievement( ACHIEVEMENT_TF_DEMOMAN_KILL_BUILDING_DIRECT_HIT );
						}
					}
				}
			}
		}
	}
	else
	{
		IGameEvent * event = gameeventmanager->CreateEvent( "object_detonated" );

		if ( event )
		{
			CTFPlayer *pTFPlayer = GetOwner();
			if ( pTFPlayer )
			{
				event->SetInt( "userid", pTFPlayer->GetUserID() );
			}
			event->SetInt( "objecttype", GetType() ); // object type
			event->SetInt( "index", entindex() );	// object entity index

			gameeventmanager->FireEvent( event );
		}
	}

	// Don't create gibs if it reversed back to a toolbox
	if ( IsUsingReverseBuild() && ( DMG_CRUSH & info.GetDamageType() ) != 0 )
	{
		CTFAmmoPack *pAmmoPack = CreateAmmoPack( "models/weapons/w_models/w_toolbox.mdl", GetObjectInfo( ObjectType() )->m_iMetalToDropInGibs );
		if ( pAmmoPack )
		{
			pAmmoPack->SetBodygroup( 1, 1 );
		}

		CObjectSapper *pSapper = GetSapper();
		if ( pSapper )
		{
			pSapper->Explode();
		}
	}
	else
	{
		// Do an explosion.
		Explode();
	}

	// Stats tracking for strange items.
	EconEntity_OnOwnerKillEaterEvent( dynamic_cast<CEconEntity *>( info.GetWeapon() ),
									  pScorer,
									  GetOwner(),
									  kKillEaterEvent_BuildingDestroyed );

	UTIL_Remove( this );
}

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

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

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

//-----------------------------------------------------------------------------
// Purpose: Return true if the Owning CTeam should clean this object up automatically
//-----------------------------------------------------------------------------
bool CBaseObject::ShouldAutoRemove( void )
{
	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
	CreateBuildPoints();
}

CObjectSapper* CBaseObject::GetSapper( void )
{
	if ( !HasSapper() )
		return NULL;

	return dynamic_cast< CObjectSapper* >( FirstMoveChild() );
}

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

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseObject::IsPlasmaDisabled( void )
{
	return m_bPlasmaDisable;
}

//-----------------------------------------------------------------------------
void CBaseObject::OnAddSapper( void )
{
	// Assume we can only build 1 sapper per object
	Assert( m_bHasSapper == false );

	m_bHasSapper = true;

	CTFPlayer *pPlayer = GetBuilder();

	if ( pPlayer )
	{
		//pPlayer->HintMessage( HINT_OBJECT_YOUR_OBJECT_SAPPED, true );
		pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_SPY_SAPPER, GetResponseRulesModifier() );
	}

	UpdateDisabledState();
}

//-----------------------------------------------------------------------------
void CBaseObject::OnRemoveSapper( void )
{
	m_bHasSapper = false;
	UpdateDisabledState();
}

//-----------------------------------------------------------------------------
int CBaseObject::GetUpgradeMetalRequired()
{
	return GetObjectInfo( GetType() )->m_UpgradeCost;
}

//-----------------------------------------------------------------------------
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: 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);

		CTakeDamageInfo info( inputdata.pCaller, inputdata.pActivator, vec3_origin, GetAbsOrigin(), iDamage, DMG_GENERIC );
		Killed( info );
	}
}

//-----------------------------------------------------------------------------
// 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: 
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CBaseObject::InputSetBuilder( inputdata_t &inputdata )
{
	CTFPlayer *pPlayer = ToTFPlayer( inputdata.pActivator );
	if ( GetBuilder() == NULL && pPlayer != NULL )
	{
		SetBuilder( pPlayer );
		ChangeTeam( pPlayer->GetTeamNumber() );
		pPlayer->AddObject( this );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CBaseObject::InputShow( inputdata_t &inputdata )
{
	RemoveEffects( EF_NODRAW );
	UpdateDisabledState();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CBaseObject::InputHide( inputdata_t &inputdata )
{
	AddEffects( EF_NODRAW );
	SetDisabled( true );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : 
// Output : did this wrench hit do any work on the object?
//-----------------------------------------------------------------------------
bool CBaseObject::InputWrenchHit( CTFPlayer *pPlayer, CTFWrench *pWrench, Vector hitLoc )
{
	Assert( pPlayer );
	if ( !pPlayer )
		return false;

	bool bDidWork = false;

	if ( HasSapper() )
	{
		// do damage to any attached buildings
		CTakeDamageInfo info( pPlayer, pPlayer, pWrench, WRENCH_DMG_VS_SAPPER, DMG_CLUB, TF_DMG_WRENCH_FIX );

		IHasBuildPoints *pBPInterface = dynamic_cast< IHasBuildPoints * >( this );
		int iNumObjects = pBPInterface->GetNumObjectsOnMe();
		for ( int iPoint=0;iPoint<iNumObjects;iPoint++ )
		{
			CBaseObject *pObject = GetBuildPointObject( iPoint );

			if ( pObject && pObject->IsHostileUpgrade() )
			{
				int iBeforeHealth = pObject->GetHealth();

				pObject->TakeDamage( info );

				// This should always be true
				if ( iBeforeHealth != pObject->GetHealth() )
				{
					bDidWork = true;
					Assert( bDidWork );
				}
			}
		}
	}
	else if ( IsUpgrading() )
	{
		bDidWork = OnWrenchHit( pPlayer, pWrench, hitLoc );
//		bDidWork = false;
	}
	else if ( IsBuilding() )
	{
		OnConstructionHit( pPlayer, pWrench, hitLoc );
		bDidWork = true;
	}
	else
	{
		// upgrade, refill, repair damage
		bDidWork = OnWrenchHit( pPlayer, pWrench, hitLoc );
	}

	if ( bDidWork )
	{
		pPlayer->m_AchievementData.AddTargetToHistory( this );
	}

	return bDidWork;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseObject::OnWrenchHit( CTFPlayer *pPlayer, CTFWrench *pWrench, Vector hitLoc )
{
	bool bRepairHit = false;
	bool bUpgradeHit = false;

	bRepairHit = Command_Repair( pPlayer, pWrench->GetRepairValue() );

	if ( !bRepairHit )
	{
		bUpgradeHit = CheckUpgradeOnHit( pPlayer );
	}

	DoWrenchHitEffect( hitLoc, bRepairHit, bUpgradeHit );

	return bUpgradeHit || bRepairHit;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::DoWrenchHitEffect( Vector hitLoc, bool bRepairHit, bool bUpgradeHit )
{
	if ( bRepairHit )
	{
		// Play a repair hit effect.
		CPVSFilter filter( hitLoc );
		TE_TFParticleEffect( filter, 0.0f, "nutsnbolts_repair", hitLoc, QAngle(0,0,0) );
	}
	else if ( bUpgradeHit )
	{
		// Play an upgrade hit effect.
		CPVSFilter filter( hitLoc );
		TE_TFParticleEffect( filter, 0.0f, "nutsnbolts_upgrade", hitLoc, QAngle(0,0,0) );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseObject::CheckUpgradeOnHit( CTFPlayer *pPlayer )
{
	if ( !CanBeUpgraded() )
		return false;

	if ( m_bCarryDeploy )
		return false;

	if ( CanBeUpgraded( pPlayer ) )
	{
		int iPlayerMetal = pPlayer->GetAmmoCount( TF_AMMO_METAL );
		int nMaxToAdd = tf_obj_upgrade_per_hit.GetInt();
		if ( TFGameRules() && TFGameRules()->IsPowerupMode() )
		{
			nMaxToAdd *= 2;
		}		
		CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, nMaxToAdd, upgrade_rate_mod );
		int iAmountToAdd = MIN( nMaxToAdd, iPlayerMetal );

		if ( iAmountToAdd > ( m_iUpgradeMetalRequired - m_iUpgradeMetal ) )
			iAmountToAdd = ( m_iUpgradeMetalRequired - m_iUpgradeMetal );

		if ( tf_cheapobjects.GetBool() == false && !ShouldQuickBuild() )
		{
			pPlayer->RemoveAmmo( iAmountToAdd, TF_AMMO_METAL );
		}

		// testing quick builds for engineers in Raid mode
		if ( TFGameRules() && !TFGameRules()->IsPVEModeControlled( pPlayer ) )
		{
#ifdef TF_RAID_MODE
			if ( TFGameRules()->IsRaidMode() )
			{
				iAmountToAdd = 200;
			}
#endif
			
			if ( TFGameRules()->GameModeUsesUpgrades() && TFGameRules()->IsQuickBuildTime() )
			{
				iAmountToAdd = 200;
			}
		}

		m_iUpgradeMetal += iAmountToAdd;

		bool bDidWork = false;
		if ( iAmountToAdd > 0 )
		{
			bDidWork = true;
		}

		if ( m_iUpgradeMetal >= m_iUpgradeMetalRequired )
		{
			IGameEvent * event = gameeventmanager->CreateEvent( "player_upgradedobject" );
			if ( event )
			{
				event->SetInt( "userid", pPlayer->GetUserID() );
				event->SetInt( "object", ObjectType() );
				event->SetInt( "index", entindex() );
				event->SetBool( "isbuilder", pPlayer == GetBuilder() );

				gameeventmanager->FireEvent( event );
			}

			StartUpgrading();
			m_iUpgradeMetal = 0;
		}

		return bDidWork;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CBaseObject::CanBeUpgraded( CTFPlayer *pPlayer )
{
	// Already upgrading
	if ( IsUpgrading() )
		return false;

	if ( IsMiniBuilding() || IsDisposableBuilding() )
		return false;

	// only engineers
	if ( !ClassCanBuild( pPlayer->GetPlayerClass()->GetClassIndex(), GetType() ) )
		return false;

	// max upgraded
	if ( m_iUpgradeLevel >= OBJ_MAX_UPGRADE_LEVEL )
		return false;

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Separated so it can be triggered by wrench hit or by vgui screen
//-----------------------------------------------------------------------------
bool CBaseObject::Command_Repair( CTFPlayer *pActivator, float flRepairMod )
{
	if ( !CanBeRepaired() )
		return false;

	const float flRepairToMetalRatio = 3.0f;		// Amount Repaired per metal
	float flTargetHeal = 100.0f * flRepairMod;
	int iAmountToHeal = MIN( flTargetHeal, GetMaxHealth() - RoundFloatToInt( GetHealth() ) );

	// repair the building
	int iRepairCost = ceil( (float)( iAmountToHeal ) / flRepairToMetalRatio );

	TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::Command_Repair ( %f / %d ) - cost = %d\n", gpGlobals->curtime, 
		GetHealth(),
		GetMaxHealth(),
		iRepairCost ) );

	if ( iRepairCost > 0 )
	{
		if ( iRepairCost > pActivator->GetBuildResources() )
		{
			iRepairCost = pActivator->GetBuildResources();
		}

		pActivator->RemoveBuildResources( iRepairCost );

		float flNewHealth = MIN( GetMaxHealth(), m_flHealth + ( iRepairCost * flRepairToMetalRatio ) );

		if ( pActivator != GetBuilder() )
		{
			float flAmountRepaired = flNewHealth - m_flHealth;
			pActivator->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_REPAIR_TEAM_GRIND, floor( flAmountRepaired ) );
		}

		SetHealth( flNewHealth );

		return ( iRepairCost > 0 );
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Upgrade this object a single level
//-----------------------------------------------------------------------------
void CBaseObject::StartUpgrading( void )
{
	// Increase level
	m_iUpgradeLevel++;

	if ( GetHighestUpgradeLevel() < m_iUpgradeLevel )
	{
		m_iHighestUpgradeLevel = m_iUpgradeLevel;
	}

	// more health
	if ( !m_bCarryDeploy && !IsUsingReverseBuild() )
	{
		int iMaxHealth = GetMaxHealthForCurrentLevel();
		SetMaxHealth( iMaxHealth );
		SetHealth( iMaxHealth );
	}

	const char *pUpgradeSound = GetObjectInfo( ObjectType() )->m_pUpgradeSound;
	if ( pUpgradeSound && *pUpgradeSound )
	{
		EmitSound( pUpgradeSound );
	}

	if ( ( !m_bWasMapPlaced || ( m_iUpgradeLevel > (m_nDefaultUpgradeLevel+1) ) ) )
	{
		SetActivity( ACT_OBJ_UPGRADING );

		float flConstructionTime = ( ShouldQuickBuild() ? 0 : GetObjectInfo( ObjectType() )->m_flUpgradeDuration );
		float flReverseBuildingConstructionSpeed = GetReversesBuildingConstructionSpeed();
		flConstructionTime /= ( flReverseBuildingConstructionSpeed == 0.0f ? 1.0f : flReverseBuildingConstructionSpeed );

		m_flUpgradeCompleteTime = gpGlobals->curtime + flConstructionTime;
	}
	else
	{
		m_flUpgradeCompleteTime = gpGlobals->curtime;	//asap
	}

	RemoveAllGestures();

	if ( TFGameRules() && TFGameRules()->IsInTraining() && 
		TFGameRules()->GetTrainingModeLogic() && 
		GetOwner() && GetOwner()->IsFakeClient() == false )
	{
		TFGameRules()->GetTrainingModeLogic()->OnPlayerUpgradedBuilding( GetOwner(), this );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::FinishUpgrading( void )
{
	const char *pUpgradeSound = GetObjectInfo( ObjectType() )->m_pUpgradeSound;
	if ( pUpgradeSound && *pUpgradeSound )
	{
		EmitSound( pUpgradeSound );
	}

	if ( IsUsingReverseBuild() )
	{
		m_iUpgradeLevel--;
		DoReverseBuild();
	}
}

//-----------------------------------------------------------------------------
// Playing the upgrade animation
//-----------------------------------------------------------------------------
void CBaseObject::UpgradeThink( void )
{
	if ( gpGlobals->curtime > m_flUpgradeCompleteTime )
	{
		FinishUpgrading();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Handles health upgrade for objects we've already built
//-----------------------------------------------------------------------------
void CBaseObject::ApplyHealthUpgrade( void )
{
	CTFPlayer *pTFPlayer = GetOwner();
	if ( !pTFPlayer )
		return;

	int iHealth = GetMaxHealthForCurrentLevel();
	SetMaxHealth( iHealth );
	SetHealth( iHealth );

	//DevMsg( "%i\n", GetMaxHealth() );
}

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

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

	switch ( m_Activity )
	{
	default:
		{
			if ( IsUpgrading() )
			{
				desiredActivity = ACT_OBJ_UPGRADING;
			}
			else if ( IsPlacing() )
			{
				/*
				if (1 || m_bPlacementOK )
				{
					desiredActivity = ACT_OBJ_PLACING;
				}
				else
				{
					desiredActivity = ACT_OBJ_IDLE;
				}
				*/
			}
			else if ( IsBuilding() )
			{
				desiredActivity = ACT_OBJ_ASSEMBLING;
			}
			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( CBaseEntity *pEntity, int iPoint, Vector &vecOrigin )
{
	m_hBuiltOnEntity = pEntity;
	m_iBuiltOnPoint = iPoint;

	int iAttachment = 0;

	if ( m_hBuiltOnEntity.Get() )
	{
		CTFPlayer *pTFPlayer = ToTFPlayer( pEntity );
		if ( pTFPlayer )
		{
			iAttachment = pTFPlayer->LookupAttachment( "head" );
		}
		else
		{
			CBaseAnimating *pAnimate = dynamic_cast<CBaseAnimating*>( pEntity );
			if ( pAnimate )
			{
				iAttachment = pAnimate->LookupBone( "weapon_bone" );
				if ( iAttachment >= 1 )
				{
					FollowEntity( m_hBuiltOnEntity.Get() );
				}
			}
			
			// Parent ourselves to the object
			IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>( pEntity );
			Assert( pBPInterface );
			if ( pBPInterface )
			{
				iAttachment = pBPInterface->GetBuildPointAttachmentIndex( iPoint );

				// re-link to the build points if the sapper is already built
				if ( !( IsPlacing() || IsBuilding() ) )
				{
					pBPInterface->SetObjectOnBuildPoint( m_iBuiltOnPoint, this );
				}
			}
		}

		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();
		}

		SetupAttachedVersion();
	}

	Assert( m_hBuiltOnEntity.Get() == 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() );
	if ( !(pObject->m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) )
	{
		pObject->SpawnControlPanels();
	}
	pObject->SetHealth( pObject->GetMaxHealth() );
	pObject->FinishedBuilding();
	pObject->AttachObjectToObject( this, iPoint, vecOrigin );
	//pObject->m_fObjectFlags |= OF_CANNOT_BE_DISMANTLED;

	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();
}

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 );
	}
}

int CBaseObject::DrawDebugTextOverlays(void) 
{
	int text_offset = BaseClass::DrawDebugTextOverlays();

	if (m_debugOverlays & OVERLAY_TEXT_BIT) 
	{
		char tempstr[512];

		Q_snprintf( tempstr, sizeof( tempstr ),"Health: %f / %d ( %.1f )", GetHealth(), GetMaxHealth(), (float)GetHealth() / (float)GetMaxHealth() );
		EntityText(text_offset,tempstr,0);
		text_offset++;

		CTFPlayer *pBuilder = GetBuilder();

		Q_snprintf( tempstr, sizeof( tempstr ),"Built by: (%d) %s",
			pBuilder ? pBuilder->entindex() : -1,
			pBuilder ? pBuilder->GetPlayerName() : "invalid builder" );
		EntityText(text_offset,tempstr,0);
		text_offset++;

		if ( IsBuilding() )
		{
			Q_snprintf( tempstr, sizeof( tempstr ),"Build Rate: %.1f", GetConstructionMultiplier() );
			EntityText(text_offset,tempstr,0);
			text_offset++;
		}
	}
	return text_offset;

}

//-----------------------------------------------------------------------------
// Purpose: Change build orientation
//-----------------------------------------------------------------------------
void CBaseObject::RotateBuildAngles( void )
{
	// rotate the build angles by 90 degrees ( final angle calculated after we network this )
	m_iDesiredBuildRotations++;
	m_iDesiredBuildRotations = m_iDesiredBuildRotations % 4;
}

//-----------------------------------------------------------------------------
// Purpose: called on edge cases to see if we need to change our disabled state
//-----------------------------------------------------------------------------
void CBaseObject::UpdateDisabledState( void )
{
	const bool bShouldBeEnabled = !m_bHasSapper
							   && !m_bPlasmaDisable
							   && (!TFGameRules()->RoundHasBeenWon() || TFGameRules()->GetWinningTeam() == GetTeamNumber());

	SetDisabled( !bShouldBeEnabled );
}

//-----------------------------------------------------------------------------
// Purpose: called when our disabled state changes
//-----------------------------------------------------------------------------
void CBaseObject::SetDisabled( bool bDisabled )
{
	if ( bDisabled && !m_bDisabled )
	{
		OnStartDisabled();
	}
	else if ( !bDisabled && m_bDisabled )
	{
		OnEndDisabled();
	}

	m_bDisabled = bDisabled;
}

//-----------------------------------------------------------------------------
void CBaseObject::SetPlasmaDisabled( float flDuration )
{
	m_bPlasmaDisable = true;
	m_flPlasmaDisableTime = gpGlobals->curtime + flDuration;
	UpdateDisabledState();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::OnStartDisabled( void )
{
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::OnEndDisabled( void )
{
}


//-----------------------------------------------------------------------------
// Purpose: Called when the model changes, find new attachments for the children
//-----------------------------------------------------------------------------
void CBaseObject::ReattachChildren( void )
{
	// Go through and store the children one by one, then reattach them.  We need
	// to store them like this because if we didnt and instead went through and
	// reattached them as we iterated over them we could get into a state where we have
	// children A and B and A has B as a sibling and B has NULL has a sibling,
	//  but we reattach B first which set's B's sibling to A creating a infinite loop
	CUtlVector<CBaseEntity*> vecChildren;
	for (CBaseEntity *pChild = FirstMoveChild(); pChild; pChild = pChild->NextMovePeer())
	{
		if( vecChildren.Find( pChild ) != vecChildren.InvalidIndex() )
		{
			AssertMsg( 0, "Cyclic siblings found when reattaching children!" );
			break;
		}

		vecChildren.AddToTail( pChild );
	}

	int iNumBuildPoints = GetNumBuildPoints();
	FOR_EACH_VEC( vecChildren, i )
	{
		CBaseObject *pObject = dynamic_cast<CBaseObject *>( vecChildren[i] );

		if ( !pObject )
		{
			continue;
		}

		Assert( pObject->GetParent() == this );

		// get the type
		int iObjectType = pObject->GetType();

		bool bReattached = false;

		Vector vecDummy;

		for ( int j = 0; j < iNumBuildPoints && bReattached == false; j++ )
		{
			// Can this object build on this point?
			if ( CanBuildObjectOnBuildPoint( j, iObjectType ) )
			{
				pObject->AttachObjectToObject( this, j, vecDummy );
				bReattached = true;
			}
		}

		// if we can't find an attach for the child, remove it and print an error
		if ( bReattached == false )
		{
			if ( m_bCarried && ( pObject->GetType() == OBJ_ATTACHMENT_SAPPER ) )
			{
				pObject->ResetPlacement();
			}
			else
			{
				pObject->DestroyObject();
				Assert( !"Couldn't find attachment point on upgraded object for existing child.\n" );
			}
		}
	}
}

void CBaseObject::SetModel( const char *pModel )
{
	// Skip if we're already the proper model
	if ( V_strcmp( GetModelName().ToCStr(), pModel ) == 0 )
		return;

	BaseClass::SetModel( pModel );

	// Clear out the gib list and create a new one.
	m_aGibs.Purge();
	BuildGibList( m_aGibs, GetModelIndex(), 1.0f, COLLISION_GROUP_NONE );

	CObjectSapper *pSapper = GetSapper();
	if ( pSapper )
	{
		pSapper->OnGoActive();
	}
}


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

	InitializeMapPlacedObject();
}

//-----------------------------------------------------------------------------
// 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();

	// Set the skin
	m_nSkin = ( GetTeamNumber() == TF_TEAM_RED ) ? 0 : 1;
}

//-----------------------------------------------------------------------------
// Purpose: Turns the object into one carried by someone.
//-----------------------------------------------------------------------------
void CBaseObject::MakeCarriedObject( CTFPlayer *pCarrier )
{
	if ( pCarrier )
	{
		// Make the object inactive.
		m_bCarried = true;
		m_bCarryDeploy = false;
		pCarrier->m_Shared.SetCarriedObject( this );
		m_iHealthOnPickup = m_iHealth; // If we are damaged, we want to remember how much damage we had sustained.

		// Remove screens.
		DestroyScreens();

		// Mount it to the player.
		FollowEntity( pCarrier );

		IGameEvent * event = gameeventmanager->CreateEvent( "player_carryobject" );
		if ( event )
		{
			event->SetInt( "userid", pCarrier->GetUserID() );
			event->SetInt( "object", GetType() );
			event->SetInt( "index", entindex() );	// object entity index

			gameeventmanager->FireEvent( event, true );	// don't send to clients
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Turns the object into one carried by someone.
//-----------------------------------------------------------------------------
void CBaseObject::DropCarriedObject( CTFPlayer* pCarrier )
{
	m_bCarried = false;
	m_bCarryDeploy = false;

	if ( pCarrier )
	{
		pCarrier->m_Shared.SetCarriedObject( NULL );
	}

	StopFollowingEntity();
}

//-----------------------------------------------------------------------------
// Purpose: Instantly build and upgrade this object
//-----------------------------------------------------------------------------
void CBaseObject::DoQuickBuild( bool bForceMax /* = false */ )
{
	if ( IsBuilding() )
	{
		FinishedBuilding();
	}

	int iTargetLevel = ( ( ( TFGameRules() && TFGameRules()->IsQuickBuildTime() ) || bForceMax ) ? OBJ_MAX_UPGRADE_LEVEL : GetUpgradeLevel() );

	if ( CanBeUpgraded( GetOwner() ) )
	{
		for ( int i = GetUpgradeLevel(); i < iTargetLevel; i++ )
		{
			StartUpgrading();
		}
	}
	else
	{
		int iMaxHealth = GetMaxHealthForCurrentLevel();
		SetMaxHealth( iMaxHealth );
		SetHealth( iMaxHealth );
	}
}

//-----------------------------------------------------------------------------
// Builds instantly under certain conditions/modes
//-----------------------------------------------------------------------------
bool CBaseObject::ShouldQuickBuild( void )
{
	if ( TFGameRules() )
	{
		if ( GetType() == OBJ_ATTACHMENT_SAPPER )
			return false;

#ifdef STAGING_ONLY
		if ( GetType() == OBJ_SPY_TRAP )
			return false;
#endif

		if ( TFGameRules()->IsQuickBuildTime() )
		{
			return true;
		}

		if ( TFGameRules()->IsMannVsMachineMode() )
		{
			if ( GetTeamNumber() == TF_TEAM_PVE_INVADERS )
			{
				// Engineer bots in MvM deploy pre-built sentries that build up at the normal rate
				return m_bForceQuickBuild;
			}

			if ( m_bCarryDeploy || TFGameRules()->State_Get() == GR_STATE_BETWEEN_RNDS )
			{
				return true;
			}
		}
	}

	return m_bForceQuickBuild;
}

void CBaseObject::DoReverseBuild( void )
{
	m_iHighestUpgradeLevel = m_iUpgradeLevel;
	m_iUpgradeMetal = 0;

	int iMaxHealth = GetMaxHealthForCurrentLevel();
	SetMaxHealth( iMaxHealth );
	if ( GetHealth() > iMaxHealth )
	{
		SetHealth( iMaxHealth );
	}

	if ( m_iUpgradeLevel > 1 )
	{
		m_iUpgradeLevel--;
		StartUpgrading();
	}
	else
	{
		m_bBuilding = true;
		m_bCarryDeploy = false;
		m_flTotalConstructionTime = m_flConstructionTimeLeft = GetTotalTime();
		m_flConstructionStartTime = gpGlobals->curtime;
		SetStartBuildingModel();
		SetControlPanelsActive( false );
	}
}

float CBaseObject::GetReversesBuildingConstructionSpeed( void )
{
	CObjectSapper *pSapper = GetSapper();
	if ( !pSapper )
		return 0.0f;

	return pSapper->GetReversesBuildingConstructionSpeed();
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseObject::InputEnable( inputdata_t &inputdata )
{
	if ( IsDisabled() )
	{
		UpdateDisabledState();
		if ( !IsDisabled() )
		{
			OnGoActive();
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseObject::InputDisable( inputdata_t &inputdata )
{
	if ( !IsDisabled() )
	{
		SetDisabled( true );
		OnGoInactive();
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CBaseObject::GetMaxHealthForCurrentLevel( void )
{
	int iMaxHealth = IsMiniBuilding() ? GetMiniBuildingStartingHealth() : GetBaseHealth();
	if ( GetOwner() && !m_bDisposableBuilding )
	{
		CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwner(), iMaxHealth, mult_engy_building_health );
	}
	
	if ( !IsMiniBuilding() && ( GetUpgradeLevel() > 1 ) )
	{
		float flMultiplier = pow( UPGRADE_LEVEL_HEALTH_MULTIPLIER, GetUpgradeLevel() - 1 );
		iMaxHealth = (int)( iMaxHealth * flMultiplier );
	}

	return iMaxHealth;
}
