//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Clients CBaseObject
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "c_baseobject.h"
#include "c_tf_player.h"
#include "hud.h"
#include "c_tf_team.h"
#include "engine/IEngineSound.h"
#include "particles_simple.h"
#include "functionproxy.h"
#include "IEffects.h"
#include "model_types.h"
#include "particlemgr.h"
#include "particle_collision.h"
#include "c_tf_weapon_builder.h"
#include "ivrenderview.h"
#include "ObjectControlPanel.h"
#include "engine/ivmodelinfo.h"
#include "c_te_effect_dispatch.h"
#include "toolframework_client.h"
#include "tf_hud_building_status.h"
#include "cl_animevent.h"
#include "eventlist.h"
#include "c_obj_sapper.h"
#include "tf_gamerules.h"
#include "tf_hud_spectator_extras.h"
#include "tf_proxyentity.h"

// NVNT for building forces
#include "haptics/haptic_utils.h"

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

// forward declarations
void ToolFramework_RecordMaterialParams( IMaterial *pMaterial );

#define MAX_VISIBLE_BUILDPOINT_DISTANCE		(400 * 400)

// Remove aliasing of name due to shared code
#undef CBaseObject

IMPLEMENT_AUTO_LIST( IBaseObjectAutoList );

IMPLEMENT_CLIENTCLASS_DT(C_BaseObject, DT_BaseObject, CBaseObject)
	RecvPropInt(RECVINFO(m_iHealth)),
	RecvPropInt(RECVINFO(m_iMaxHealth)),
	RecvPropInt(RECVINFO(m_bHasSapper)),
	RecvPropInt(RECVINFO(m_iObjectType)),
	RecvPropBool(RECVINFO(m_bBuilding)),
	RecvPropBool(RECVINFO(m_bPlacing)),
	RecvPropBool(RECVINFO(m_bCarried)),
	RecvPropBool(RECVINFO(m_bCarryDeploy)),
	RecvPropBool(RECVINFO(m_bMiniBuilding)),
	RecvPropFloat(RECVINFO(m_flPercentageConstructed)),
	RecvPropInt(RECVINFO(m_fObjectFlags)),
	RecvPropEHandle(RECVINFO(m_hBuiltOnEntity)),
	RecvPropInt( RECVINFO( m_bDisabled ) ),
	RecvPropEHandle( RECVINFO( m_hBuilder ) ),
	RecvPropVector( RECVINFO( m_vecBuildMaxs ) ),
	RecvPropVector( RECVINFO( m_vecBuildMins ) ),
	RecvPropInt( RECVINFO( m_iDesiredBuildRotations ) ),
	RecvPropInt( RECVINFO( m_bServerOverridePlacement ) ),
	RecvPropInt( RECVINFO(m_iUpgradeLevel) ),
	RecvPropInt( RECVINFO(m_iUpgradeMetal) ),
	RecvPropInt( RECVINFO(m_iUpgradeMetalRequired) ),
	RecvPropInt( RECVINFO(m_iHighestUpgradeLevel) ),
	RecvPropInt( RECVINFO(m_iObjectMode) ),
	RecvPropBool( RECVINFO( m_bDisposableBuilding ) ),
	RecvPropBool( RECVINFO( m_bWasMapPlaced ) ),
	RecvPropBool( RECVINFO( m_bPlasmaDisable ) ),
END_RECV_TABLE()

ConVar cl_obj_test_building_damage( "cl_obj_test_building_damage", "-1", FCVAR_CHEAT, "debug building damage", true, -1, true, BUILDING_DAMAGE_LEVEL_CRITICAL );

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
C_BaseObject::C_BaseObject(  )
{
	m_YawPreviewState = YAW_PREVIEW_OFF;
	m_bBuilding = false;
	m_bPlacing = false;
	m_flPercentageConstructed = 0;
	m_fObjectFlags = 0;
	m_iOldUpgradeLevel = 0;

	m_flCurrentBuildRotation = 0;

	m_damageLevel = BUILDING_DAMAGE_LEVEL_NONE;

	m_iLastPlacementPosValid = -1;

	m_iObjectMode = 0;

	m_bCarryDeploy = false;
	m_bOldCarryDeploy = false;

	m_bMiniBuilding = false;
	m_bDisposableBuilding = false;

	m_vecBuildForward = vec3_origin;
	m_flBuildDistance = 0.0f;

	m_flInvisibilityPercent = 0.f;

	m_bWasMapPlaced = false;

	m_hDamageEffects = NULL;

	m_bPlasmaDisable = false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
C_BaseObject::~C_BaseObject( void )
{
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BaseObject::Spawn( void )
{
	BaseClass::Spawn();

	m_bServerOverridePlacement = true;	// assume valid at the start
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BaseObject::UpdateOnRemove( void )
{
	StopAnimGeneratedSounds();

	DestroyBoneAttachments();

	CTFHudSpectatorExtras *pSpectatorExtras = GET_HUDELEMENT( CTFHudSpectatorExtras );
	if ( pSpectatorExtras )
 	{
		pSpectatorExtras->RemoveEntity( entindex() );
 	}

	BaseClass::UpdateOnRemove();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BaseObject::PreDataUpdate( DataUpdateType_t updateType )
{
	BaseClass::PreDataUpdate( updateType );

	m_iOldHealth = m_iHealth;
	m_hOldOwner = GetOwner();
	m_bWasActive = ShouldBeActive();
	m_bWasBuilding = m_bBuilding;
	m_bOldDisabled = m_bDisabled;
	m_bWasPlacing = m_bPlacing;

	m_nObjectOldSequence = GetSequence();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BaseObject::OnDataChanged( DataUpdateType_t updateType )
{
	if (updateType == DATA_UPDATE_CREATED)
	{
		CreateBuildPoints();
		// NVNT if the local player created this send a created effect
		if(IsOwnedByLocalPlayer() &&haptics)
		{
			haptics->ProcessHapticEvent(3, "Game", "Build", GetClassname());
		}
	}

	BaseClass::OnDataChanged( updateType );

	// did we just pick up the object?
	if ( !m_bWasPlacing && m_bPlacing )
	{
		m_iLastPlacementPosValid = -1;
	}

	// Did we just finish building?
	if ( m_bWasBuilding && !m_bBuilding )
	{
		FinishedBuilding();
	}
	else if ( !m_bWasBuilding && m_bBuilding )
	{
		ResetClientsideFrame();
	}

	// Did we just go active?
	bool bShouldBeActive = ShouldBeActive();
	if ( !m_bWasActive && bShouldBeActive )
	{
		OnGoActive();
	}
	else if ( m_bWasActive && !bShouldBeActive )
	{
		OnGoInactive();
	}

	if ( m_bDisabled != m_bOldDisabled )
	{
		if ( m_bDisabled )
		{
			OnStartDisabled();
		}
		else
		{
			OnEndDisabled();
		}
	}

	if ( !IsBuilding() && m_iHealth != m_iOldHealth )
	{
		// recalc our damage particle state
		BuildingDamageLevel_t damageLevel = CalculateDamageLevel();

		if ( damageLevel != m_damageLevel )
		{
			UpdateDamageEffects( damageLevel );

			m_damageLevel = damageLevel;
		}
	}

	if ( m_bCarryDeploy != m_bOldCarryDeploy )
	{
		m_bOldCarryDeploy = m_bCarryDeploy;
		if ( !m_bCarryDeploy )
		{
			// Update our damage effects when we're done redeploying.
			UpdateDamageEffects( CalculateDamageLevel() );
		}
	}

	if ( m_iHealth > m_iOldHealth && m_iHealth == m_iMaxHealth )
	{
		// If we were just fully healed, remove all decals
		RemoveAllDecals();
	}

	if ( GetOwner() == C_TFPlayer::GetLocalTFPlayer() )
	{
		IGameEvent *event = gameeventmanager->CreateEvent( "building_info_changed" );
		if ( event )
		{
			event->SetInt( "building_type", GetType() );
			event->SetInt( "object_mode", GetObjectMode() );
			gameeventmanager->FireEventClientSide( event );
		}
	}

	if ( IsPlacing() && GetSequence() != m_nObjectOldSequence )
	{
		// Ignore server sequences while placing
		OnPlacementStateChanged( m_iLastPlacementPosValid > 0 );
	}

	if ( m_iOldUpgradeLevel != m_iUpgradeLevel )
	{
		UpgradeLevelChanged();
		m_iOldUpgradeLevel = m_iUpgradeLevel;
	}
	// NVNT building status
	if(IsOwnedByLocalPlayer()) {
		if(m_bWasBuilding!=m_bBuilding) {
			if(m_bBuilding && haptics) {
				haptics->ProcessHapticEvent(3, "Game", "Building", GetClassname());
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BaseObject::SetDormant( bool bDormant )
{
	BaseClass::SetDormant( bDormant );
	//ENTITY_PANEL_ACTIVATE( "analyzed_object", !bDormant );
}

#define TF_OBJ_BODYGROUPTURNON			1
#define TF_OBJ_BODYGROUPTURNOFF			0

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : origin - 
//			angles - 
//			event - 
//			*options - 
//-----------------------------------------------------------------------------
void C_BaseObject::FireEvent( const Vector& origin, const QAngle& angles, int event, const char *options )
{
	switch ( event )
	{
	default:
		{
			BaseClass::FireEvent( origin, angles, event, options );
		}
		break;
	case TF_OBJ_PLAYBUILDSOUND:
		{
			EmitSound( options );
		}
		break;
	case TF_OBJ_ENABLEBODYGROUP:
		{
			int index_ = FindBodygroupByName( options );
			if ( index_ >= 0 )
			{
				SetBodygroup( index_, TF_OBJ_BODYGROUPTURNON );
			}
		}
		break;
	case TF_OBJ_DISABLEBODYGROUP:
		{
			int index_ = FindBodygroupByName( options );
			if ( index_ >= 0 )
			{
				SetBodygroup( index_, TF_OBJ_BODYGROUPTURNOFF );
			}
		}
		break;
	case TF_OBJ_ENABLEALLBODYGROUPS:
	case TF_OBJ_DISABLEALLBODYGROUPS:
		{
			// Start at 1, because body 0 is the main .mdl body...
			// Is this the way we want to do this?
			int count = GetNumBodyGroups();
			for ( int i = 1; i < count; i++ )
			{
				int subpartcount = GetBodygroupCount( i );
				if ( subpartcount == 2 )
				{
					SetBodygroup( i, 
						( event == TF_OBJ_ENABLEALLBODYGROUPS ) ?
						TF_OBJ_BODYGROUPTURNON : TF_OBJ_BODYGROUPTURNOFF );
				}
				else
				{
					DevMsg( "TF_OBJ_ENABLE/DISABLEBODY GROUP:  %s has a group with %i subparts, should be exactly 2\n",
						GetClassname(), subpartcount );
				}
			}
		}
		break;
	}
}


const char* C_BaseObject::GetStatusName() const
{
	return GetObjectInfo( GetType() )->m_AltModes[GetObjectMode()].pszStatusName;
}

//-----------------------------------------------------------------------------
// Purpose: placement state has changed, update the model
//-----------------------------------------------------------------------------
void C_BaseObject::OnPlacementStateChanged( bool bValidPlacement )
{
	if ( bValidPlacement )
	{
		// NVNT if the local player placed this send a created effect
		if(IsOwnedByLocalPlayer()&&haptics)
		{
			haptics->ProcessHapticEvent(3, "Game", "Placed", GetClassname());
		}
		SetActivity( ACT_OBJ_PLACING );
	}
	else
	{
		SetActivity( ACT_OBJ_IDLE );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BaseObject::Simulate( void )
{
	if ( IsPlacing() && !MustBeBuiltOnAttachmentPoint() )
	{
		int iValidPlacement = ( IsPlacementPosValid() && ServerValidPlacement() ) ? 1 : 0;

		if ( m_iLastPlacementPosValid != iValidPlacement )
		{
			m_iLastPlacementPosValid = iValidPlacement;
			OnPlacementStateChanged( m_iLastPlacementPosValid > 0 );
		}

		// We figure out our own placement pos, but we still leave it to the server to 
		// do collision with other entities and nobuild triggers, so that sets the 
		// placement animation

		SetLocalOrigin( m_vecBuildOrigin );
		InvalidateBoneCache();

		// Clear out our origin and rotation interpolation history
		// so we don't pop when we teleport in the actual position from the server

		CInterpolatedVar< Vector > &interpolator = GetOriginInterpolator();
		interpolator.ClearHistory();

		CInterpolatedVar<QAngle> &rotInterpolator = GetRotationInterpolator();
		rotInterpolator.ClearHistory();
	}
	else if ( !IsPlacing() && !IsCarried() && m_iLastPlacementPosValid == 0 )
	{
		// HACK HACK: This sentry has been placed, but was placed on the server before the client updated
		// from the carry position to see that was a valid placement.
		// It missed its chance to set the correct activity, so we're doing it now.
		SetActivity( ACT_OBJ_RUNNING );

		// Check if the activity was valid because it might have still been using the older placement model
		if ( GetActivity() != ACT_INVALID )
		{
			// Remember to retest our placement, but don't keep forcing the running activity
			m_iLastPlacementPosValid = -1;
		}
	}

	BaseClass::Simulate();
}

//-----------------------------------------------------------------------------
// Purpose: Return false if the server is telling us we can't place right now
// could be due to placing in a nobuild or respawn room
//-----------------------------------------------------------------------------
bool C_BaseObject::ServerValidPlacement( void )
{
	return m_bServerOverridePlacement;
}

bool C_BaseObject::WasLastPlacementPosValid( void )
{
	if ( MustBeBuiltOnAttachmentPoint() )
	{
		return ( !IsEffectActive(EF_NODRAW) );
	}
	
	return ( m_iLastPlacementPosValid > 0 );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int C_BaseObject::DrawModel( int flags )
{
	int drawn;

	// If we're a brush-built, map-defined object chain up to baseentity draw
	if ( modelinfo->GetModelType( GetModel() ) == mod_brush )
	{
		drawn = CBaseEntity::DrawModel(flags);
	}
	else
	{
		drawn = BaseClass::DrawModel(flags);
	}

	HighlightBuildPoints( flags );

	return drawn;
}

float C_BaseObject::GetReversesBuildingConstructionSpeed( void )
{
	if ( HasSapper() )
	{
		C_ObjectSapper *pSapper = dynamic_cast< C_ObjectSapper* >( FirstMoveChild() );
		if ( pSapper )
		{
			return pSapper->GetReversesBuildingConstructionSpeed();
		}
	}

	return 0.0f;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BaseObject::HighlightBuildPoints( int flags )
{
	C_TFPlayer *pLocal = C_TFPlayer::GetLocalTFPlayer();
	if ( !pLocal )
		return;

	if ( !GetNumBuildPoints() || !InLocalTeam() )
		return;

	C_TFWeaponBuilder *pBuilderWpn = dynamic_cast< C_TFWeaponBuilder * >( pLocal->GetActiveWeaponForSelection() );
	if ( !pBuilderWpn )
		return;
	if ( !pBuilderWpn->IsPlacingObject() )
		return;
	C_BaseObject *pPlacementObj = pBuilderWpn->GetPlacementModel();
	if ( !pPlacementObj || pPlacementObj == this )
		return;

	// Near enough?
	if ( (GetAbsOrigin() - pLocal->GetAbsOrigin()).LengthSqr() < MAX_VISIBLE_BUILDPOINT_DISTANCE )
	{
		bool bRestoreModel = false;
		Vector vecPrevAbsOrigin = pPlacementObj->GetAbsOrigin();
		QAngle vecPrevAbsAngles = pPlacementObj->GetAbsAngles();

		Vector orgColor;
		render->GetColorModulation( orgColor.Base() );
		float orgBlend = render->GetBlend();

		bool bSameTeam = ( pPlacementObj->GetTeamNumber() == GetTeamNumber() );

		if ( pPlacementObj->IsHostileUpgrade() && bSameTeam )
		{
			// Don't hilight hostile upgrades on friendly objects
			return;
		}
		else if ( !bSameTeam )
		{
			// Don't hilight upgrades on enemy objects
			return;
		}

		// Any empty buildpoints?
		for ( int i = 0; i < GetNumBuildPoints(); i++ )
		{
			// Can this object build on this point?
			if ( CanBuildObjectOnBuildPoint( i, pPlacementObj->GetType() ) )
			{
				Vector vecBPOrigin;
				QAngle vecBPAngles;
				if ( GetBuildPoint(i, vecBPOrigin, vecBPAngles) )
				{
					pPlacementObj->InvalidateBoneCaches();

					Vector color( 0, 255, 0 );
					render->SetColorModulation(	color.Base() );
					float frac = fmod( gpGlobals->curtime, 3 );
					frac *= 2 * M_PI;
					frac = cos( frac );
					render->SetBlend( (175 + (int)( frac * 75.0f )) / 255.0 );

					// FIXME: This truly sucks! The bone cache should use
					// render location for this computation instead of directly accessing AbsAngles
					// Necessary for bone cache computations to work
					pPlacementObj->SetAbsOrigin( vecBPOrigin );
					pPlacementObj->SetAbsAngles( vecBPAngles );

					modelrender->DrawModel( 
						flags, 
						pPlacementObj,
						pPlacementObj->GetModelInstance(),
						pPlacementObj->index, 
						pPlacementObj->GetModel(),
						vecBPOrigin,
						vecBPAngles,
						pPlacementObj->m_nSkin,
						pPlacementObj->m_nBody,
						pPlacementObj->m_nHitboxSet
						);

					bRestoreModel = true;
				}
			}
		}

		if ( bRestoreModel )
		{
			pPlacementObj->SetAbsOrigin(vecPrevAbsOrigin);
			pPlacementObj->SetAbsAngles(vecPrevAbsAngles);
			pPlacementObj->InvalidateBoneCaches();

			render->SetColorModulation( orgColor.Base() );
			render->SetBlend( orgBlend );
		}
	}
}

//-----------------------------------------------------------------------------
// Builder preview...
//-----------------------------------------------------------------------------
void C_BaseObject::ActivateYawPreview( bool enable )
{
	m_YawPreviewState = enable ? YAW_PREVIEW_ON : YAW_PREVIEW_WAITING_FOR_UPDATE;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BaseObject::PreviewYaw( float yaw )
{
	m_fYawPreview = yaw;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool C_BaseObject::IsPreviewingYaw() const
{
	return m_YawPreviewState != YAW_PREVIEW_OFF;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
BuildingDamageLevel_t C_BaseObject::CalculateDamageLevel( void )
{
	float flPercentHealth = (float)m_iHealth / (float)m_iMaxHealth;

	BuildingDamageLevel_t damageLevel = BUILDING_DAMAGE_LEVEL_NONE;

	if ( flPercentHealth < 0.25 )
	{
		damageLevel = BUILDING_DAMAGE_LEVEL_CRITICAL;
	}
	else if ( flPercentHealth < 0.45 )
	{
		damageLevel = BUILDING_DAMAGE_LEVEL_HEAVY;
	}
	else if ( flPercentHealth < 0.65 )
	{
		damageLevel = BUILDING_DAMAGE_LEVEL_MEDIUM;
	}
	else if ( flPercentHealth < 0.85 )
	{
		damageLevel = BUILDING_DAMAGE_LEVEL_LIGHT;
	}

	if ( cl_obj_test_building_damage.GetInt() >= 0 )
	{
		damageLevel = (BuildingDamageLevel_t)cl_obj_test_building_damage.GetInt();
	}

	return damageLevel;
}

/*
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BaseObject::Release( void )
{
	// Remove any reticles on this entity
	C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
	if ( pPlayer )
	{
		pPlayer->Remove_Target( this );
	}

	BaseClass::Release();
}
*/

//-----------------------------------------------------------------------------
// Ownership: 
//-----------------------------------------------------------------------------
C_TFPlayer *C_BaseObject::GetOwner()
{
	return m_hBuilder;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool C_BaseObject::IsOwnedByLocalPlayer() const
{
	if ( !m_hBuilder )
		return false;

	return ( m_hBuilder == C_TFPlayer::GetLocalTFPlayer() );
}

//-----------------------------------------------------------------------------
// Purpose: Add entity to visibile entities list
//-----------------------------------------------------------------------------
void C_BaseObject::AddEntity( void )
{
	// If set to invisible, skip. Do this before resetting the entity pointer so it has 
	// valid data to decide whether it's visible.
	if ( !ShouldDraw() )
	{
		return;
	}

	// Update the entity position
	//UpdatePosition();

	// Yaw preview
	if (m_YawPreviewState != YAW_PREVIEW_OFF)
	{
		// This piece of code makes it so we keep using the preview
		// until we get a network update which matches the update value
		if (m_YawPreviewState == YAW_PREVIEW_WAITING_FOR_UPDATE)
		{
			if (fmod( fabs(GetLocalAngles().y - m_fYawPreview), 360.0f) < 1.0f)
			{
				m_YawPreviewState = YAW_PREVIEW_OFF;
			}
		}

		if (GetLocalOrigin().y != m_fYawPreview)
		{
			SetLocalAnglesDim( Y_INDEX, m_fYawPreview );
			InvalidateBoneCache();
		}
	}

	// Create flashlight effects, etc.
	CreateLightEffects();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BaseObject::Select( void )
{
	C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
	pPlayer->SetSelectedObject( this );
}

void C_BaseObject::ResetClientsideFrame( void )
{
	SetCycle( GetReversesBuildingConstructionSpeed() != 0.0f ? 1.0f : 0.0f );
}

//-----------------------------------------------------------------------------
// Sends client commands back to the server: 
//-----------------------------------------------------------------------------
void C_BaseObject::SendClientCommand( const char *pCmd )
{
	char szbuf[128];
	Q_snprintf( szbuf, sizeof( szbuf ), "objcmd %d %s", entindex(), pCmd );
  	engine->ClientCmd(szbuf);
}


//-----------------------------------------------------------------------------
// Purpose: Get a text description for the object target
//-----------------------------------------------------------------------------
const char *C_BaseObject::GetTargetDescription( void ) const
{
	return GetStatusName();
}

//-----------------------------------------------------------------------------
// Purpose: Get a text description for the object target (more verbose)
//-----------------------------------------------------------------------------
const char *C_BaseObject::GetIDString( void )
{
	m_szIDString[0] = 0;
	RecalculateIDString();
	return m_szIDString;
}


//-----------------------------------------------------------------------------
// It's a valid ID target when it's building 
//-----------------------------------------------------------------------------
bool C_BaseObject::IsValidIDTarget( void )
{
	return InSameTeam( C_TFPlayer::GetLocalTFPlayer() ) && m_bBuilding;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BaseObject::RecalculateIDString( void )
{
	// Subclasses may have filled this out with a string
	if ( !m_szIDString[0] )
	{
		Q_strncpy( m_szIDString, GetTargetDescription(), sizeof(m_szIDString) );
	}

	// Have I taken damage?
	if ( m_iHealth < m_iMaxHealth )
	{
		char szHealth[ MAX_ID_STRING ];
		if ( m_bBuilding )
		{
			Q_snprintf( szHealth, sizeof(szHealth), "\nConstruction at %.0f percent\nHealth at %.0f percent", (m_flPercentageConstructed * 100), ceil(((float)m_iHealth / (float)m_iMaxHealth) * 100) );
		}
		else
		{
			Q_snprintf( szHealth, sizeof(szHealth), "\nHealth at %.0f percent", ceil(((float)m_iHealth / (float)m_iMaxHealth) * 100) );
		}
		Q_strncat( m_szIDString, szHealth, sizeof(m_szIDString), COPY_ALL_CHARACTERS );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Player has waved his crosshair over this entity. Display appropriate hints.
//-----------------------------------------------------------------------------
void C_BaseObject::DisplayHintTo( C_BasePlayer *pPlayer )
{
	bool bHintPlayed = false;

	C_TFPlayer *pTFPlayer = ToTFPlayer(pPlayer);
	if ( InSameTeam( pPlayer ) )
	{
		// We're looking at a friendly object. 

		if ( HasSapper() )
		{
			bHintPlayed = pPlayer->HintMessage( HINT_OBJECT_HAS_SAPPER, true, true );
		}

		if ( pTFPlayer->IsPlayerClass( TF_CLASS_ENGINEER ) )
		{
			// I'm an engineer.

			// If I'm looking at a constructing object, let me know I can help build it (but not 
			// if I built it myself, since I've already got that hint from the wrench).
			if ( !bHintPlayed && IsBuilding() && GetBuilder() != pTFPlayer )
			{
				bHintPlayed = pPlayer->HintMessage( HINT_ENGINEER_USE_WRENCH_ONOTHER, false, true );
			}

			// If it's damaged, I can repair it
			if ( !bHintPlayed && !IsBuilding() && GetHealth() < GetMaxHealth() )
			{
				bHintPlayed = pPlayer->HintMessage( HINT_ENGINEER_REPAIR_OBJECT, false, true );
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BaseObject::GetGlowEffectColor( float *r, float *g, float *b )
{
	if ( TFGameRules() )
	{
		TFGameRules()->GetTeamGlowColor( GetTeamNumber(), *r, *g, *b );
	}
	else
	{
		*r = 0.76f;
		*g = 0.76f;
		*b = 0.76f;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Does this object have a sapper on it
//-----------------------------------------------------------------------------
bool C_BaseObject::HasSapper( void )
{
	return m_bHasSapper;
}

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

void C_BaseObject::OnStartDisabled()
{
}

void C_BaseObject::OnEndDisabled()
{
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_BaseObject::GetTargetIDString( OUT_Z_BYTECAP( iMaxLenInBytes ) wchar_t *sIDString, int iMaxLenInBytes, bool bSpectator )
{
	Assert( iMaxLenInBytes >= sizeof(sIDString[0]) );
	sIDString[0] = '\0';

	C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();

	if ( !pLocalPlayer )
		return;
	
	if ( pLocalPlayer->InSameDisguisedTeam( this ) || pLocalPlayer->IsPlayerClass( TF_CLASS_SPY ) || bSpectator )
	{
		wchar_t wszBuilderName[ MAX_PLAYER_NAME_LENGTH ];

		const char *pszStatusName = GetStatusName();
		const wchar_t *wszObjectName = g_pVGuiLocalize->Find( pszStatusName );

		bool bHasMode = false;
		const char *printFormatString = "#TF_playerid_object";

		if ( IsMiniBuilding() && !IsDisposableBuilding() )
		{
			printFormatString = "#TF_playerid_object_mini";
		}

		const wchar_t *wszModeName = L"";
		const CObjectInfo* pObjectInfo = GetObjectInfo( GetType() );
		if ( pObjectInfo && (pObjectInfo->m_iNumAltModes > 0) )
		{
			const char *pszModeName = pObjectInfo->m_AltModes[GetObjectMode()].pszModeName;
			wszModeName = g_pVGuiLocalize->Find( pszModeName );
			printFormatString = "TF_playerid_object_mode";
			bHasMode = true;
		}

		if ( !wszObjectName )
		{
			wszObjectName = L"";
		}

		C_BasePlayer *pBuilder = GetOwner();

		if ( pBuilder )
		{
			g_pVGuiLocalize->ConvertANSIToUnicode( pBuilder->GetPlayerName(), wszBuilderName, sizeof(wszBuilderName) );
		}
		else
		{
			wszBuilderName[0] = '\0';
		}

		// building or live, show health
		wchar_t * localizedString = g_pVGuiLocalize->Find( printFormatString );
		if ( localizedString )
		{
			if ( bHasMode )
			{
				g_pVGuiLocalize->ConstructString( sIDString, iMaxLenInBytes, localizedString,
					3, wszObjectName, wszBuilderName, wszModeName );
			}
			else
			{
				g_pVGuiLocalize->ConstructString( sIDString, iMaxLenInBytes, localizedString,
					2, wszObjectName, wszBuilderName );
			}
		}

	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BaseObject::GetTargetIDDataString( OUT_Z_BYTECAP(iMaxLenInBytes) wchar_t *sDataString, int iMaxLenInBytes )
{
	Assert( iMaxLenInBytes >= sizeof(sDataString[0]) );
	sDataString[0] = '\0';

	C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
	if ( !pLocalPlayer )
		return;

	// Sentryguns have models for each level, so we don't show it in their target ID.
	bool bShowLevel = ( GetType() != OBJ_SENTRYGUN );

	wchar_t wszLevel[32];
	if ( bShowLevel )
	{
		_snwprintf( wszLevel, ARRAYSIZE(wszLevel) - 1, L"%d", m_iUpgradeLevel );
		wszLevel[ ARRAYSIZE(wszLevel)-1 ] = '\0';
	}

	if ( m_iUpgradeLevel >= 3 )
	{
		if ( bShowLevel )
		{
			g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find("#TF_playerid_object_level"),
				1,
				wszLevel );
		}
		return;
	}

	wchar_t wszBuilderName[ MAX_PLAYER_NAME_LENGTH ];
	wchar_t wszObjectName[ 32 ];
	wchar_t wszUpgradeProgress[ 32 ];

	g_pVGuiLocalize->ConvertANSIToUnicode( GetStatusName(), wszObjectName, sizeof(wszObjectName) );

	C_BasePlayer *pBuilder = GetOwner();

	if ( pBuilder )
	{
		g_pVGuiLocalize->ConvertANSIToUnicode( pBuilder->GetPlayerName(), wszBuilderName, sizeof(wszBuilderName) );
	}
	else
	{
		wszBuilderName[0] = '\0';
	}

	// level 1 and 2 show upgrade progress
	if ( !IsMiniBuilding() && !IsDisposableBuilding() )
	{
		_snwprintf( wszUpgradeProgress, ARRAYSIZE(wszUpgradeProgress) - 1, L"%d / %d", m_iUpgradeMetal, GetUpgradeMetalRequired() );
		wszUpgradeProgress[ ARRAYSIZE(wszUpgradeProgress)-1 ] = '\0';
		if ( bShowLevel )
		{
			g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find("#TF_playerid_object_upgrading_level"),
				2,
				wszLevel,
				wszUpgradeProgress );
		}
		else
		{
			g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find("#TF_playerid_object_upgrading"),
				1,
				wszUpgradeProgress );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int C_BaseObject::GetDisplayPriority( void )
{
	return GetObjectInfo( GetType() )->m_iDisplayPriority;	
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const char *C_BaseObject::GetHudStatusIcon( void )
{
	return GetObjectInfo( GetType() )->m_pHudStatusIcon;	
}

ConVar cl_obj_fake_alert( "cl_obj_fake_alert", "0", 0, "", true, BUILDING_HUD_ALERT_NONE, true, MAX_BUILDING_HUD_ALERT_LEVEL-1 );

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
BuildingHudAlert_t C_BaseObject::GetBuildingAlertLevel( void )
{
	float flHealthPercent = GetHealth() / GetMaxHealth();

	BuildingHudAlert_t alertLevel = BUILDING_HUD_ALERT_NONE;

	if ( HasSapper() )
	{
		alertLevel = BUILDING_HUD_ALERT_SAPPER;
	}
	else if ( !IsBuilding() && flHealthPercent < 0.33 )
	{
		alertLevel = BUILDING_HUD_ALERT_VERY_LOW_HEALTH;
	}
	else if ( !IsBuilding() && flHealthPercent < 0.66 )
	{
		alertLevel = BUILDING_HUD_ALERT_LOW_HEALTH;
	}

	BuildingHudAlert_t iFakeAlert = (BuildingHudAlert_t)cl_obj_fake_alert.GetInt();

	if ( iFakeAlert > BUILDING_HUD_ALERT_NONE &&
		iFakeAlert < MAX_BUILDING_HUD_ALERT_LEVEL )
	{
		alertLevel = iFakeAlert;
	}

	return alertLevel;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
ShadowType_t C_BaseObject::ShadowCastType( void ) 
{
	if ( GetInvisibilityLevel() == 1.f )
		return SHADOWS_NONE;

	return BaseClass::ShadowCastType();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float C_BaseObject::GetInvisibilityLevel( void )
{
#ifdef STAGING_ONLY
	C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
	C_TFPlayer *pOwner = GetOwner();
	if ( pLocalPlayer && pLocalPlayer->m_Shared.InCond( TF_COND_STEALTHED_PHASE ) && pLocalPlayer != pOwner )
		return 1.f;
#endif // STAGING_ONLY

	return m_flInvisibilityPercent;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BaseObject::SetInvisibilityLevel( float flValue )
{
	m_flPrevInvisibilityPercent = m_flInvisibilityPercent;
	m_flInvisibilityPercent = clamp( flValue, 0.f, 1.f );
}

//-----------------------------------------------------------------------------
// Purpose: find the anim events that may have started sounds, and stop them.
//-----------------------------------------------------------------------------
void C_BaseObject::StopAnimGeneratedSounds( void )
{
	MDLCACHE_CRITICAL_SECTION();

	CStudioHdr *pStudioHdr = GetModelPtr();
	if ( !pStudioHdr )
		return;

	mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( GetSequence() );
	if ( seqdesc.numevents == 0 )
		return;

	float flCurrentCycle = GetCycle();
	mstudioevent_t *pevent = GetEventIndexForSequence( seqdesc );

	for (int i = 0; i < (int)seqdesc.numevents; i++)
	{
		if ( pevent[i].cycle < flCurrentCycle )
		{
			if ( pevent[i].event == CL_EVENT_SOUND || pevent[i].event == AE_CL_PLAYSOUND )
			{
				StopSound( entindex(), pevent[i].options );
			}
		}
	}
}

//============================================================================================================
// POWER PROXY
//============================================================================================================
class CObjectPowerProxy : public CResultProxy
{
public:
	bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
	void OnBind( void *pC_BaseEntity );

private:
	CFloatInput	m_Factor;
};

bool CObjectPowerProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
	if (!CResultProxy::Init( pMaterial, pKeyValues ))
		return false;

	if (!m_Factor.Init( pMaterial, pKeyValues, "scale", 1 ))
		return false;

	return true;
}

void CObjectPowerProxy::OnBind( void *pRenderable )
{
	// Find the view angle between the player and this entity....
	IClientRenderable *pRend = (IClientRenderable *)pRenderable;
	C_BaseEntity *pEntity = pRend->GetIClientUnknown()->GetBaseEntity();
	C_BaseObject *pObject = dynamic_cast<C_BaseObject*>(pEntity);
	if (!pObject)
		return;

	SetFloatResult(  m_Factor.GetFloat() );

	if ( ToolsEnabled() )
	{
		ToolFramework_RecordMaterialParams( GetMaterial() );
	}
}

EXPOSE_INTERFACE( CObjectPowerProxy, IMaterialProxy, "ObjectPower" IMATERIAL_PROXY_INTERFACE_VERSION );

//-----------------------------------------------------------------------------
// Control screen 
//-----------------------------------------------------------------------------
class CBasicControlPanel : public CObjectControlPanel
{
	DECLARE_CLASS( CBasicControlPanel, CObjectControlPanel );

public:
	CBasicControlPanel( vgui::Panel *parent, const char *panelName );
};


DECLARE_VGUI_SCREEN_FACTORY( CBasicControlPanel, "basic_control_panel" );


//-----------------------------------------------------------------------------
// Constructor: 
//-----------------------------------------------------------------------------
CBasicControlPanel::CBasicControlPanel( vgui::Panel *parent, const char *panelName )
	: BaseClass( parent, "CBasicControlPanel" ) 
{
}


//-----------------------------------------------------------------------------
// Purpose: Used for spy invisiblity material
//-----------------------------------------------------------------------------
class CBuildingInvisProxy : public CBaseInvisMaterialProxy
{
public:
	virtual void OnBind( C_BaseEntity *pBaseEntity ) OVERRIDE;
};

//-----------------------------------------------------------------------------
// Purpose: 
// Input :
//-----------------------------------------------------------------------------
void CBuildingInvisProxy::OnBind( C_BaseEntity *pBaseEntity )
{
	if ( !m_pPercentInvisible )
		return;

	if ( !pBaseEntity->IsBaseObject() )
		return;

	C_BaseObject *pObject = static_cast< C_BaseObject* >( pBaseEntity );
	if ( !pObject )
		return;

	CTFPlayer *pOwner = ToTFPlayer( pObject->GetOwner() );
	if ( !pOwner )
	{
		m_pPercentInvisible->SetFloatValue( 0.0f );
		return;
	}

	m_pPercentInvisible->SetFloatValue( pObject->GetInvisibilityLevel() );
}

EXPOSE_INTERFACE( CBuildingInvisProxy, IMaterialProxy, "building_invis" IMATERIAL_PROXY_INTERFACE_VERSION );