//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Clients CBaseObject
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "c_baseobject.h"
#include "c_basetfplayer.h"
#include "hud.h"
#include "c_tfteam.h"
#include "engine/IEngineSound.h"
#include "particles_simple.h"
#include "functionproxy.h"
#include "IEffects.h"
#include "c_hint_events.h"
#include "model_types.h"
#include "particlemgr.h"
#include "particle_collision.h"
#include "env_objecteffects.h"
#include "basetfvehicle.h"
#include "c_weapon_builder.h"
#include "ivrenderview.h"
#include "ObjectControlPanel.h"
#include "engine/ivmodelinfo.h"
#include "c_te_effect_dispatch.h"

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

#define MAX_VISIBLE_BUILDPOINT_DISTANCE		(400 * 400)

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

IMPLEMENT_CLIENTCLASS_DT(C_BaseObject, DT_BaseObject, CBaseObject)
	RecvPropInt(RECVINFO(m_iHealth)),
	RecvPropInt(RECVINFO(m_iMaxHealth)),
	RecvPropInt(RECVINFO(m_bHasSapper)),
	RecvPropInt(RECVINFO(m_iObjectType)),
	RecvPropInt(RECVINFO(m_bBuilding)),
	RecvPropInt(RECVINFO(m_bPlacing)),
	RecvPropFloat(RECVINFO(m_flPercentageConstructed)),
	RecvPropInt(RECVINFO(m_fObjectFlags)),
	RecvPropInt(RECVINFO(m_bDeteriorating)),
	RecvPropEHandle(RECVINFO(m_hBuiltOnEntity)),
	RecvPropInt(RECVINFO( m_takedamage ) ),
	RecvPropInt( RECVINFO( m_bDisabled ) ),
	RecvPropEHandle( RECVINFO( m_hBuilder ) ),
END_RECV_TABLE()

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
C_BaseObject::C_BaseObject(  )
{
	m_flDamageFlash = 0;
	m_YawPreviewState = YAW_PREVIEW_OFF;
	m_bBuilding = false;
	m_bPlacing = false;
	m_flPercentageConstructed = 0;
	m_flNextEffect = 0;
	m_bOldSapper = m_bHasSapper = false;
	m_fObjectFlags = 0;
	m_bDeteriorating = false;
	m_ThermalMaterial.Init("player/thermal/thermal",TEXTURE_GROUP_CLIENT_EFFECTS);
}

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

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

	m_iOldHealth = m_iHealth;
 	m_bOldSapper = m_bHasSapper;
	m_hOldOwner = GetOwner();
	m_bWasActive = ShouldBeActive();
	m_bWasBuilding = m_bBuilding;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BaseObject::OnDataChanged( DataUpdateType_t updateType )
{
	if (updateType == DATA_UPDATE_CREATED)
	{
		if ( !IS_MINIMAP_PANEL_DEFINED( ) && !(m_fObjectFlags & OF_SUPPRESS_APPEAR_ON_MINIMAP) )
		{
			CONSTRUCT_MINIMAP_PANEL( "minimap_object", MINIMAP_OBJECTS );
		}

		CreateBuildPoints();
	}

	BaseClass::OnDataChanged( updateType );

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

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

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

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool C_BaseObject::OffsetObjectOrigin( Vector& origin )
{
	if ( !m_bBuilding )
		return false;
	
	if ( inv_demo.GetBool() )
		return false;

	Vector vecWorldMins, vecWorldMaxs;
	CollisionProp()->WorldSpaceAABB( &vecWorldMins, &vecWorldMaxs );
	float flSize = vecWorldMaxs.z - vecWorldMins.z;
	origin.z -= (flSize * (1 - m_flPercentageConstructed));

	// If we're building, fake sliding the object out of the ground
	return true;
}


const char* C_BaseObject::GetStatusName() const
{
	return GetObjectInfo( GetType() )->m_pStatusName;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int C_BaseObject::DrawModel( int flags )
{
	Vector vRealOrigin = GetLocalOrigin();
	Vector vOrigin = vRealOrigin;
	bool needOriginReset = OffsetObjectOrigin( vOrigin );
	if ( needOriginReset )
	{
		SetLocalOrigin( vOrigin );
		InvalidateBoneCache();
	}

	int drawn;
	C_BaseTFPlayer *pLocal = C_BaseTFPlayer::GetLocalPlayer();
	if ( pLocal && pLocal->IsUsingThermalVision() )
	{
		modelrender->ForcedMaterialOverride( m_ThermalMaterial );
		drawn = BaseClass::DrawModel(flags);
		modelrender->ForcedMaterialOverride( NULL );
	}
	else
	{
		// 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);
		}
	}

	// Restore faked origin
	if ( needOriginReset )
	{
		SetLocalOrigin( vRealOrigin );
	}

	// If we were drawn, draw building effects if we're building, or damage effects if we're damaged
	if ( drawn && (m_flNextEffect < gpGlobals->curtime) )
	{
		// Haxory LOD
		C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
		if ( (GetAbsOrigin() - pPlayer->GetAbsOrigin()).LengthSqr() < lod_effect_distance.GetFloat() )
		{
			if ( IsBuilding() )
			{
				DrawBuildEffects();
			}
			if ( !m_bPlacing && !m_bBuilding )
			{
				if ( !HasPowerup( POWERUP_EMP ) )
				{
					DrawRunningEffects();
				}

				if ( GetHealth() < GetMaxHealth() )
				{
					DrawDamageEffects();
				}
			}
		}
	}

	HighlightBuildPoints( flags );

	return drawn;
}

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

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

	C_WeaponBuilder *pBuilderWpn = dynamic_cast< C_WeaponBuilder * >( 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();

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

					// HACK: Fixup angles on the HL2 model we're using
					if ( !strcmp( modelinfo->GetModelName( pPlacementObj->GetModel() ), "models/items/HealthKit.mdl" ) )
					{
						vecBPAngles.x += 90;
					}

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


//-----------------------------------------------------------------------------
// Exit points for mounted vehicles....
//-----------------------------------------------------------------------------
void C_BaseObject::GetExitPoint( CBaseEntity *pPlayer, int nBuildPoint, Vector *pAbsPosition, QAngle *pAbsAngles )
{
	Assert(0);
}


//-----------------------------------------------------------------------------
// Purpose: Overridden to allow for brush-built map defined objects
//-----------------------------------------------------------------------------
bool C_BaseObject::IsIdentityBrush( void )
{
	return false;
}

//-----------------------------------------------------------------------------
// 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: This is called to get the initial builder yaw...
//-----------------------------------------------------------------------------
float C_BaseObject::GetInitialBuilderYaw()
{
	return GetAbsAngles().y;
}

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

	bool bNewEntity = (updateType == DATA_UPDATE_CREATED);
	if ( bNewEntity )
	{
		m_flAttackTime = -1000;
	}

	// Determine if we're under attack
	if ( !bNewEntity )
	{
		if ( m_iHealth < m_iOldHealth )
		{
			// Deteriorating objects don't play sounds
			if ( !IsDeteriorating() )
			{
				m_flAttackTime = gpGlobals->curtime;
			}
		}
		else if ( m_iHealth > m_iOldHealth && m_iHealth == m_iMaxHealth )
		{
			// If we were just fully healed, remove all decals
			RemoveAllDecals();
		}
	}

	if ( m_bHasSapper ) 
	{
		// Play a specific sound for a sapper...
		if ( m_bOldSapper != m_bHasSapper )
		{
			// Don't create these for dragonsteeth
			if ( InLocalTeam() && GetType() != OBJ_DRAGONSTEETH )
			{
				// Play a sound.
				CLocalPlayerFilter filter;
				EmitSound( filter, SOUND_FROM_LOCAL_PLAYER, "BaseObject.SapperDestroyingTeamBuilding" );

				MinimapCreateTempTrace( "minimap_under_attack", MINIMAP_PERSONAL_ORDERS, GetAbsOrigin() );
			}
		}
	}

	// Notify the hint system of the object being built.
	if ( bNewEntity && GetOwner() && ( GetOwner() == C_BasePlayer::GetLocalPlayer() ) )
	{
		C_HintEvent_ObjectBuiltByLocalPlayer event( this );
		GlobalHintEvent( &event );
	}
}

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

	BaseClass::Release();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool C_BaseObject::IsUnderAttack( )
{
	// It's under attack for the 3 seconds after the last attack time
	return (gpGlobals->curtime - m_flAttackTime) < 5.0f;
}

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

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

	return ( m_hBuilder == C_BaseTFPlayer::GetLocalPlayer() );
}

//-----------------------------------------------------------------------------
// 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_BaseTFPlayer *pPlayer = C_BaseTFPlayer::GetLocalPlayer();
	pPlayer->SetSelectedObject( this );
}

//-----------------------------------------------------------------------------
// 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)
//-----------------------------------------------------------------------------
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_BaseTFPlayer::GetLocalPlayer() ) && 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 ( IsDeteriorating() )
		{
			Q_snprintf( szHealth, sizeof(szHealth), "\nBUILDER LOST, DETERIORATING... %.0f percent", ceil(((float)m_iHealth / (float)m_iMaxHealth) * 100) );
		}
		else 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 );
	}

	if ( m_bHasSapper )
	{
		Q_strncat( m_szIDString, "\nUse it to remove the attached enemy object", sizeof(m_szIDString), COPY_ALL_CHARACTERS );
	}

	// If it's deteriorating, and I can buy it, tell me
	C_BaseTFPlayer *pLocalPlayer = C_BaseTFPlayer::GetLocalPlayer();
	if ( IsDeteriorating() && pLocalPlayer && ClassCanBuild( pLocalPlayer->PlayerClass(), GetType() ) )
	{
		char szBuy[ MAX_ID_STRING ];
		int iCost = CalculateObjectCost( GetType(), pLocalPlayer->GetNumObjects( GetType() ), pLocalPlayer->GetTeamNumber() );
		Q_snprintf( szBuy, sizeof(szBuy), "\nBUY THIS OBJECT FOR %d RESOURCES", iCost );
		Q_strncat( m_szIDString, szBuy, sizeof(m_szIDString), COPY_ALL_CHARACTERS );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Effects created when the object's running
//-----------------------------------------------------------------------------
void C_BaseObject::DrawRunningEffects( void )
{
	if ( !GetMaxHealth() )
		return;

	// Get the overall damage percentage
	float flDamaged = 1.0 - ((float)GetHealth() / (float)GetMaxHealth());

	// Damage attachment points
	int iSmokeAttachment, iSparkAttachment;
	Vector vecSmoke, vecSpark, vecSmokeDir, dir;
	QAngle angSmoke, angSpark;

	// Look for damage points
	iSmokeAttachment = LookupRandomAttachment( "r_smoke" );

	// Get the points
	if ( GetAttachment( iSmokeAttachment, vecSmoke, angSmoke ) )
	{
		AngleVectors( angSmoke, &vecSmokeDir);
	
		float r, g, b;
		r = g = b = random->RandomFloat( 16, 92 );

		// Smoke
		CSmartPtr<CObjectSmokeParticles> pSmokeEmitter = CObjectSmokeParticles::Create( "DrawRunningEffects 1" );
		pSmokeEmitter->SetSortOrigin( vecSmoke );
		ObjectSmokeParticle *pParticle = (ObjectSmokeParticle *) pSmokeEmitter->AddParticle( sizeof(ObjectSmokeParticle), g_Mat_DustPuff[1], vecSmoke );
		if ( pParticle )
		{
			pParticle->m_flLifetime	= 0.0f;
			pParticle->m_flDieTime	= random->RandomFloat( 2.0f, 3.0f );
			pParticle->m_uchStartSize = random->RandomFloat( 2, 3 );
			pParticle->m_uchEndSize = random->RandomFloat( 5, 10 );
			dir[0] = vecSmokeDir[0] + random->RandomFloat( -0.1f, 0.1f );
			dir[1] = vecSmokeDir[1] + random->RandomFloat( -0.1f, 0.1f );
			dir[2] = vecSmokeDir[2] + random->RandomFloat( -0.1f, 0.1f );
			pParticle->m_vecVelocity = dir * random->RandomFloat( 30.0f, 40.0f );
			pParticle->m_uchStartAlpha	= random->RandomFloat( 128,255 );
			pParticle->m_uchEndAlpha	= 0;
			pParticle->m_flRoll			= random->RandomFloat( 180, 360 );
			pParticle->m_flRollDelta	= random->RandomFloat( -1, 1 );
			pParticle->m_uchColor[0] = r;
			pParticle->m_uchColor[1] = g;
			pParticle->m_uchColor[2] = b;
			pParticle->m_vecAcceleration = Vector(0,0,10);
		}
	}

	// Sparks
	for ( float flSparks = flDamaged - 0.3; flSparks > 0; flSparks -= 0.3 )
	{
		// Get random spark attachment point
		iSparkAttachment = LookupRandomAttachment( "r_spark" );
		if ( GetAttachment( iSparkAttachment, vecSpark, angSpark ) )
		{
			g_pEffects->Sparks( vecSpark );
		}
	}

	m_flNextEffect = gpGlobals->curtime + random->RandomFloat( 0.05, 0.1 );
}

//-----------------------------------------------------------------------------
// Purpose: Effects created while the object's building itself
//-----------------------------------------------------------------------------
void C_BaseObject::DrawBuildEffects( void )
{
	m_flNextEffect = gpGlobals->curtime + 10;
}

//-----------------------------------------------------------------------------
// Purpose: Effects created when the object's damaged
//-----------------------------------------------------------------------------
void C_BaseObject::DrawDamageEffects( void )
{
	if ( !GetMaxHealth() )
		return;

	// Get the overall damage percentage
	float flDamaged = 1.0 - ((float)GetHealth() / (float)GetMaxHealth());

	// Damage attachment points
	int iSmokeAttachment, iFireAttachment, iSparkAttachment;
	Vector vecSmoke, vecFire, vecSpark, vecSmokeDir, dir;
	QAngle angSmoke, angFire, angSpark;

	// HACK: Calculate a random origin
	// This can go away when we require all objects to have damage attachment points
	Vector vecOrigin;
	CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &vecOrigin );

	// Look for damage points
	iSmokeAttachment = LookupRandomAttachment( "d_smoke" );
	iFireAttachment = LookupRandomAttachment( "d_fire" );

	// Get the points, and if we can't find 'em, use the random origin
	if ( GetAttachment( iSmokeAttachment, vecSmoke, angSmoke ) )
	{
		AngleVectors( angSmoke, &vecSmokeDir );
	}
	else
	{
		vecSmoke = vecOrigin;
		vecSmokeDir = Vector(0,0,1);
	}
	if ( !GetAttachment( iFireAttachment, vecFire, angFire ) )
	{
		vecFire = vecOrigin;
		angFire = QAngle(0,0,0);
	}
	
	float r, g, b;
	r = g = b = random->RandomFloat( 16, 92 );

	// Smoke
	CSmartPtr<CSimpleEmitter> pSmokeEmitter = CSimpleEmitter::Create( "DrawDamageEffects 1" );
	pSmokeEmitter->SetSortOrigin( vecSmoke );
	SimpleParticle *pParticle = (SimpleParticle *) pSmokeEmitter->AddParticle( sizeof(SimpleParticle), g_Mat_DustPuff[1], vecSmoke );
	if ( pParticle )
	{
		pParticle->m_flLifetime	= 0.0f;
		pParticle->m_flDieTime	= random->RandomFloat( 2.0f, 3.0f );
		pParticle->m_uchStartSize = MAX( 1, 20 * flDamaged );
		pParticle->m_uchEndSize = MAX( 10, 80 * flDamaged );
		dir[0] = vecSmokeDir[0] + random->RandomFloat( -0.2f, 0.2f );
		dir[1] = vecSmokeDir[1] + random->RandomFloat( -0.2f, 0.2f );
		dir[2] = vecSmokeDir[2] + random->RandomFloat( -0.2f, 0.2f );
		pParticle->m_vecVelocity = dir * random->RandomFloat( 60.0f, 80.0f );
		pParticle->m_uchStartAlpha	= 255;
		pParticle->m_uchEndAlpha	= 0;
		pParticle->m_flRoll			= random->RandomFloat( 180, 360 );
		pParticle->m_flRollDelta	= random->RandomFloat( -1, 1 );
		pParticle->m_uchColor[0] = r;
		pParticle->m_uchColor[1] = g;
		pParticle->m_uchColor[2] = b;
	}

	// If we're really hurt, start burning
	if ( flDamaged > 0.25 )
	{
		CSmartPtr<CObjectFireParticles> pFireEmitter = CObjectFireParticles::Create( "DrawDamageEffects 1" );
		pFireEmitter->SetSortOrigin( vecFire );
		PMaterialHandle	hSphereMaterial = pFireEmitter->GetPMaterial( "sprites/floorflame" );
		ObjectFireParticle *pParticle = (ObjectFireParticle *) pFireEmitter->AddParticle( sizeof(ObjectFireParticle), hSphereMaterial, vecFire );
		if ( pParticle )
		{
			pParticle->m_hParent = this;
			pParticle->m_iAttachmentPoint = iFireAttachment;

			pParticle->m_flLifetime	= 0.0f;
			pParticle->m_flDieTime	= 1.0;
			pParticle->m_uchStartSize = MAX( 5, 30 * (flDamaged - 0.25) );
			pParticle->m_uchEndSize = pParticle->m_uchStartSize;
			pParticle->m_vecVelocity = Vector(0,0,1);
			pParticle->m_uchStartAlpha = 255;
			pParticle->m_uchEndAlpha = 255;
			pParticle->m_flRoll	= 0;
			pParticle->m_flRollDelta = 0;
		}
	}

	// Sparks
	for ( float flSparks = flDamaged - 0.3; flSparks > 0; flSparks -= 0.3 )
	{
		// Get random spark attachment point
		iSparkAttachment = LookupRandomAttachment( "d_spark" );
		if ( !GetAttachment( iSparkAttachment, vecSpark, angSpark ) )
		{
			vecSpark = vecOrigin;
			angSpark = QAngle(0,0,0);
		}

		g_pEffects->Sparks( vecSpark );
	}

	m_flNextEffect = gpGlobals->curtime + random->RandomFloat( 0.2, 0.5 );
}

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

	int iPowered = pObject->IsPowered();

	SetFloatResult( iPowered * m_Factor.GetFloat() );
}

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" ) 
{
}