//========= Copyright © 1996-2005, Sander Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//

#include "cbase.h"
#include "rts_basebuilding.h"
#include "hl2wars/rts_globalresource.h"
#include "explode.h"
#include "particle_parse.h"
#include "hl2wars/hl2wars_player.h"

// support for nav mesh
#include "nav_mesh.h"
#include "nav_pathfind.h"
#include "nav_area.h"
#include "collisionutils.h"

#define BUILDING_ANIM		"Building_Anim"

//
// Trigger which detects entities placed in the cargo hold of the jalopy
//

class CSelectionHelper : public CMouseRTSModel
{
	DECLARE_CLASS( CSelectionHelper, CMouseRTSModel );
public:
	DECLARE_SERVERCLASS();
	DECLARE_DATADESC();
	// 
	// Creates a selection helper with the specified bounds

	static CSelectionHelper *Create( const Vector &vecOrigin, const Vector &vecMins, const Vector &vecMaxs, CBaseEntity *pOwner )
	{
		CSelectionHelper *pHelper = (CSelectionHelper *) CreateEntityByName( "rts_selection_helper" );
		if ( pHelper == NULL )
			return NULL;

		UTIL_SetOrigin( pHelper, vecOrigin );
			
		pHelper->SetOwnerEntity( pOwner );
		pHelper->SetParent( pOwner );

		pHelper->Spawn();

		UTIL_SetSize( pHelper, vecMins, vecMaxs );	

		return pHelper;
	}

	//
	// Setup the entity

	void Spawn( void )
	{
		// to be tracable on the client, we require a model
		// this is a hack for now, until I find out where this is being controlled.
		SetModelName(MAKE_STRING("models/props_combine/bunker_gun01.mdl"));
		BaseClass::Spawn();

		SetSolid( SOLID_BBOX );

		AddEffects( EF_NODRAW );
	}

	void Activate()
	{
		BaseClass::Activate();
	}

	void OnClickLeft( CBasePlayer *pActivator )
	{
		if( GetOwnerEntity() && GetOwnerEntity()->IsRTSModel() )
		{
			CMouseRTSModel *pRTSModel = assert_cast<CMouseRTSModel *>( GetOwnerEntity() );
			if( pRTSModel )
				pRTSModel->OnClickLeft( pActivator );
		}
	}

	void OnClickRight( CBasePlayer *pActivator )
	{
		if( GetOwnerEntity() && GetOwnerEntity()->IsRTSModel() )
		{
			CMouseRTSModel *pRTSModel = assert_cast<CMouseRTSModel *>( GetOwnerEntity() );
			if( pRTSModel )
				pRTSModel->OnClickRight( pActivator );
		}
	}

	// if we don't want to be solid, we should be ignored by almost all tracing rules
	virtual bool					StandardFilterIgnoreMe( void ) { return true; }

};


LINK_ENTITY_TO_CLASS( rts_selection_helper, CSelectionHelper );

BEGIN_DATADESC( CSelectionHelper )
END_DATADESC()

IMPLEMENT_SERVERCLASS_ST( CSelectionHelper, DT_SelectionHelper )
END_SEND_TABLE()


LINK_ENTITY_TO_CLASS( rts_basebuilding_model, CBaseBuildingModel );

BEGIN_DATADESC( CBaseBuildingModel )
	// function pointers
	DEFINE_THINKFUNC( BuildThink ),
	DEFINE_THINKFUNC( ConstructThink ),
	DEFINE_THINKFUNC( BreakThink ),
	DEFINE_THINKFUNC( AnimThink ),

	// keyfields
	DEFINE_KEYFIELD( m_iOwnerShip,		FIELD_INTEGER, "ownership" ),
	DEFINE_KEYFIELD( m_vOwnerShipIconTarget,	FIELD_VECTOR, "ownershipicon" ),
	DEFINE_KEYFIELD( m_iConstructionState,	FIELD_INTEGER, "constructed" ),

	DEFINE_KEYFIELD( m_iszIdleAnim, FIELD_STRING, "IdleAnim"),

	// fields
	DEFINE_FIELD( m_iConstructProgress,	FIELD_INTEGER ),
	DEFINE_FIELD( m_fConstructTime,	FIELD_FLOAT ),
	DEFINE_FIELD( m_fConstructFinished,	FIELD_TIME ),
	DEFINE_FIELD( m_iszIcon,	FIELD_STRING ),

	// explosion fields
	DEFINE_FIELD( m_nExplosionFlags,		FIELD_INTEGER ),
	DEFINE_FIELD( m_iszExplosionParticle,	FIELD_STRING ),
	DEFINE_FIELD( m_iszFireballSprite,		FIELD_STRING ),
	DEFINE_FIELD( m_sFireballSprite,		FIELD_SHORT ),
	DEFINE_FIELD( m_iMagnitude,				FIELD_INTEGER ),
	DEFINE_FIELD( m_spriteScale,			FIELD_INTEGER ),
	DEFINE_FIELD( m_fCleanUpDuration,		FIELD_FLOAT ),
	DEFINE_FIELD( m_fCleanUpTime,			FIELD_TIME ),
	DEFINE_FIELD( m_Amplitude,				FIELD_FLOAT ),
	DEFINE_FIELD( m_Frequency,				FIELD_FLOAT ),
	DEFINE_FIELD( m_Duration,				FIELD_FLOAT ),
	DEFINE_FIELD( m_Radius,					FIELD_FLOAT ),
	DEFINE_FIELD( m_fSinkSpeed,					FIELD_FLOAT ),

	// input
	DEFINE_INPUTFUNC(FIELD_INTEGER, "changeownership", InputChangeOwnerShip),

END_DATADESC()

IMPLEMENT_SERVERCLASS_ST( CBaseBuildingModel, DT_BaseBuildingModel )
	// construction info
	SendPropInt		(SENDINFO(m_iHealth), 10, SPROP_UNSIGNED ),
	SendPropInt		(SENDINFO(m_iMaxHealth), 10, SPROP_UNSIGNED ),
	SendPropInt		(SENDINFO(m_iConstructProgress), 8, SPROP_UNSIGNED ),
	SendPropInt		(SENDINFO(m_iConstructionState), 2, SPROP_UNSIGNED ),

	// normal info
	SendPropVector	(SENDINFO(m_vOwnerShipIconTarget), -1, SPROP_COORD ),
	SendPropFloat	(SENDINFO(m_fViewDistance), 0, SPROP_NOSCALE ),
	SendPropInt		(SENDINFO(m_iOwnerShip), 4, SPROP_UNSIGNED ),
	SendPropString( SENDINFO(m_iszNetIcon) ),
END_SEND_TABLE()

// ##################################################################################
//	>> Base Building Model implementation
//
// ##################################################################################
//-----------------------------------------------------------------------------
// Purpose: Construct the slots for covering
//-----------------------------------------------------------------------------
CBaseBuildingModel::CBaseBuildingModel() 
{
	m_iConstructionState = BS_CONSTRUCTED;
	m_iszIcon = NULL_STRING;
	m_iszNetIcon.GetForModify()[0] = 0;
	m_hLinkController = NULL;
	m_fCleanUpDuration = 0.0f;
	m_iszExplosionParticle = NULL_STRING;
	m_iszFireballSprite = NULL_STRING;
	m_iszSoundSelected = NULL_STRING;
	m_iszSoundDestroyed = NULL_STRING;
	m_hSelectionHelper = NULL;
	m_bUseSelectionHelper = false;
	m_hSprite = NULL;
}

CBaseBuildingModel::~CBaseBuildingModel() 
{
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseBuildingModel::UpdateOnRemove( void )
{
	// cleanup
	CSprite *pSprite = (CSprite *)m_hSprite.Get();
	if ( pSprite )
	{
		pSprite->TurnOff();
		UTIL_Remove( pSprite );
		m_hSprite = NULL;
	}	

	RemoveComponents();

	BaseClass::UpdateOnRemove();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
Class_T	CBaseBuildingModel::Classify()
{
	return CLASS_BUILDING;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseBuildingModel::IsAlive( void )
{
	return m_lifeState == LIFE_ALIVE; 
}

//-----------------------------------------------------------------------------
// Purpose: Called when spawning, after keyvalues have been handled.
//-----------------------------------------------------------------------------
void CBaseBuildingModel::Spawn( )
{
	// precache
	Precache();

	BaseClass::Spawn();

	SetConstructionState(m_iConstructionState);

	AddFlag( FL_OBJECT );		// we must be considered by enemies, so they will attack us.

	//SetCollisionGroup( COLLISION_GROUP_NPC );
	SetCollisionGroup( COLLISION_GROUP_INTERACTIVE );

	// init our life
	m_lifeState	= LIFE_ALIVE;
	m_takedamage = DAMAGE_YES;

	// setup target positions that are relative to the origin of this building
	VectorYawRotate( m_vOwnerShipIconTarget.GetForModify(), GetAbsAngles()[YAW], m_vOwnerShipIconTarget.GetForModify() );
	m_vOwnerShipIconTarget += GetAbsOrigin();

	// create a sprite
	if( m_iszIcon != NULL_STRING )
		m_hSprite = CSprite::SpriteCreate( STRING(m_iszIcon), m_vOwnerShipIconTarget, false );

	CSprite *pSprite = (CSprite *)m_hSprite.Get();
	if ( pSprite )
	{
		pSprite->SetTransparency( kRenderTransAlpha, 255, 255, 255, 255, kRenderFxNoDissipation );
		pSprite->SetBrightness( 255, 0.5f );
		pSprite->SetScale( 0.5f, 0.0f );
		pSprite->TurnOn();
	}

	if( m_bUseSelectionHelper )
	{
		CreateSelectionHelper( GetAbsOrigin(), m_vSelectionMin, m_vSelectionMax, this );
	}

	// play idle animation if we have one
	if (m_iszIdleAnim != NULL_STRING)
	{
		PropSetAnim( STRING( m_iszIdleAnim ) );
	}	
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseBuildingModel::Precache()
{
	// precache our icon
	if( m_iszIcon != NULL_STRING )
		PrecacheModel( STRING(m_iszIcon) );

	if( m_iszExplosionParticle != NULL_STRING )
		PrecacheParticleSystem(STRING(m_iszExplosionParticle));

	if ( m_iszFireballSprite != NULL_STRING )
		m_sFireballSprite = PrecacheModel( STRING( m_iszFireballSprite ) );

	if( m_iszSoundSelected != NULL_STRING )
		PrecacheScriptSound(STRING(m_iszSoundSelected) );

	if( m_iszSoundDestroyed != NULL_STRING )
		PrecacheScriptSound(STRING(m_iszSoundDestroyed) );

	BaseClass::Precache();
}

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

	// now disable nodes/nav areas we are blocking 
	//DisableNodes( true );
	UpdateNavAreas( true );	
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseBuildingModel::CreateSelectionHelper( const Vector &vecOrigin, const Vector &vecMins, const Vector &vecMaxs, CBaseEntity *pOwner )
{
	m_hSelectionHelper = CSelectionHelper::Create( vecOrigin, vecMins, vecMaxs, pOwner );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseBuildingModel::DestroySelectionHelper()
{
	UTIL_Remove( m_hSelectionHelper );
	m_hSelectionHelper = NULL;
}
//-----------------------------------------------------------------------------
// Purpose: In preconstruction state, only send to the player that has this building
//			Otherwise don't send.
//			TODO: Add psv check and a check to determine if the player has this building
//			selected.
//-----------------------------------------------------------------------------
int CBaseBuildingModel::ShouldTransmit( const CCheckTransmitInfo *pInfo )
{
	CBaseEntity *pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt );

	Assert( pRecipientEntity->IsPlayer() );

	CHL2Wars_Player *pRecipientPlayer = ToHL2WarsPlayer( pRecipientEntity );
	if( !pRecipientPlayer )
		return FL_EDICT_PVSCHECK;

	// if this is our building, we should send always if selected.
	// If in preconstruction state, do a pvs check
	if( pRecipientPlayer->GetTeamRTSID() == m_iOwnerShip )
	{
		if( pRecipientPlayer->m_HLWLocal.m_hSelected.Get() == this )
			return FL_EDICT_ALWAYS;
		else if( m_iConstructionState == BS_PRECONSTRUCTION )
			return FL_EDICT_PVSCHECK;
	}

	// if in preconstruction state and this is not our building, don't send
	if( m_iConstructionState == BS_PRECONSTRUCTION )
		return FL_EDICT_DONTSEND;

	// default to pvs check.
	return FL_EDICT_PVSCHECK;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CBaseBuildingModel::KeyValue( const char *szKeyName, const char *szValue )
{
	// health is being filtered out by cbaseprop, so we retrieve it here
	if ( FStrEq(szKeyName, "health") )
	{
		m_iHealth = atoi( szValue );
		return true;
	}

	if ( FStrEq( szKeyName, "construct_time" ) )
	{
		SetConstructTime( atof( szValue ) );
		return true;
	}

	if ( FStrEq( szKeyName, "icon" ) )
	{
		m_iszIcon = AllocPooledString(szValue);
		return true;
	}

	if ( FStrEq( szKeyName, "viewdistance" ) )
	{
		m_fViewDistance = atof(szValue);
		return true;
	}

	// sounds
	if ( FStrEq( szKeyName, "sound_selected" ) )
	{
		m_iszSoundSelected = AllocPooledString(szValue);
		return true;
	}

	if ( FStrEq( szKeyName, "sound_destroyed" ) )
	{
		m_iszSoundDestroyed = AllocPooledString(szValue);
		return true;
	}

	// selection helper
	if ( FStrEq( szKeyName, "selection_helper_min" ) )
	{
		m_bUseSelectionHelper = true;
		UTIL_StringToVector( m_vSelectionMin.Base(), szValue);
		return true;
	}

	if ( FStrEq( szKeyName, "selection_helper_max" ) )
	{
		m_bUseSelectionHelper = true;
		UTIL_StringToVector( m_vSelectionMax.Base(), szValue);
		return true;
	}

	// explosion settings
	if ( FStrEq( szKeyName, "explosion_flags" ) )
	{
		m_nExplosionFlags = atoi(szValue);
		return true;
	}

	// EF_EXPLOSION
	if ( FStrEq( szKeyName, "explosion_sprite" ) )
	{
		m_iszFireballSprite = AllocPooledString(szValue);
		return true;
	}

	if ( FStrEq( szKeyName, "explosion_magnitude" ) )
	{
		m_iMagnitude = atoi(szValue);
		m_spriteScale = (int)((m_iMagnitude - 50) * 0.6);
		return true;
	}

	// EF_PARTICLE
	if ( FStrEq( szKeyName, "explosion_particle" ) )
	{
		m_iszExplosionParticle = AllocPooledString(szValue);
		return true;
	}
	
	// EF_CINEMATIC & EF_SINK
	if( FStrEq( szKeyName, "explosion_cleanupdelay" ) )
	{
		m_fCleanUpDuration = atof(szValue);
	}

	// EF_SHAKE 
	if( FStrEq( szKeyName, "explosion_shake_radius" ) )
	{
		m_Radius = atof(szValue);
	}

	if( FStrEq( szKeyName, "explosion_shake_amplitude" ) )
	{
		m_Amplitude = atof(szValue);
	}

	if( FStrEq( szKeyName, "explosion_shake_duration" ) )
	{
		m_Duration = atof(szValue);
	}

	if( FStrEq( szKeyName, "explosion_shake_frequency" ) )
	{
		m_Frequency = atof(szValue);
	}

	// EF_SINK
	if( FStrEq( szKeyName, "explosion_sink_speed" ) )
	{
		m_fSinkSpeed = atof(szValue);
	}	

	return BaseClass::KeyValue( szKeyName, szValue );
}

//-----------------------------------------------------------------------------
// Purpose: Being called on right click
//-----------------------------------------------------------------------------
void CBaseBuildingModel::OnClickRight( CBasePlayer *pActivator ) 
{
	BaseClass::OnClickRight( pActivator );
}

//-----------------------------------------------------------------------------
// Purpose: Being called on right double click
//-----------------------------------------------------------------------------
void CBaseBuildingModel::OnClickRightDouble( CBasePlayer *pActivator ) 
{
	BaseClass::OnClickRight( pActivator );
}


//-----------------------------------------------------------------------------
// Purpose: Left click
//-----------------------------------------------------------------------------
void CBaseBuildingModel::OnClickLeft( CBasePlayer *pActivator ) 
{
	BaseClass::OnClickLeft( pActivator );

	if( m_iszSoundSelected != NULL_STRING )
	{
		CSingleUserRecipientFilter filter( pActivator );
		EmitSound( filter, entindex(), STRING(m_iszSoundSelected) );
	}

	CHL2Wars_Player *pPlayer = ToHL2WarsPlayer(pActivator);
	Assert( pPlayer );

	// we are a building and should be selected on left click
	if( pPlayer && GetOwnerShip() == pPlayer->GetTeamRTSID() )
	{
		pPlayer->ClearSelection();
		pPlayer->m_HLWLocal.m_hSelected = this;
		pPlayer->m_HLWLocal.m_iSelectionType = SELECTION_BUILDING;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Left click double
//-----------------------------------------------------------------------------
void CBaseBuildingModel::OnClickLeftDouble( CBasePlayer *pActivator ) 
{
	BaseClass::OnClickLeftDouble( pActivator );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : inputdata_t &inputdata
//-----------------------------------------------------------------------------
void CBaseBuildingModel::InputChangeOwnerShip( inputdata_t &inputdata )
{
	g_pRTSResource->UnRegisterBuilding( this, m_iOwnerShip );
	m_iOwnerShip = inputdata.value.Int();
	g_pRTSResource->RegisterBuilding( this, m_iOwnerShip );
}

// -- model building functions ( setting up )
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseBuildingModel::SetOwnership( int new_owner )
{
	m_iOwnerShip = new_owner;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseBuildingModel::SetIconTarget( Vector icon_target )
{
	m_vOwnerShipIconTarget = icon_target;
}

//------------- Construction fucntions
//-----------------------------------------------------------------------------
// Purpose: take health
//-----------------------------------------------------------------------------
int CBaseBuildingModel::TakeHealth (float flHealth, int bitsDamageType)
{
	if (!m_takedamage)
		return 0;
	
	return BaseClass::TakeHealth(flHealth, bitsDamageType);
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CBaseBuildingModel::OnTakeDamage( const CTakeDamageInfo &info )
{
	int retVal = 0;

	if (!m_takedamage)
		return 0;

	if ( info.GetDamageType() & DMG_SHOCK )
	{
		//g_pEffects->Sparks( info.GetDamagePosition(), 2, 2 );
		UTIL_Smoke( info.GetDamagePosition(), random->RandomInt( 10, 15 ), 10 );
	}

	switch( m_lifeState )
	{
	case LIFE_ALIVE:
		retVal = OnTakeDamage_Alive( info );
		if ( m_iHealth <= 0 )
		{
			// int nDeathHealth = m_iHealth;
			IPhysicsObject *pPhysics = VPhysicsGetObject();
			if ( pPhysics )
			{
				pPhysics->EnableCollisions( false );
			}
			
			Event_Killed( info );
		}
		return retVal;
		break;

	case LIFE_DYING:
		return OnTakeDamage_Dying( info );
	
	default:
	case LIFE_DEAD:
		retVal = OnTakeDamage_Dead( info );
		return retVal;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseBuildingModel::SetHealth( int amt )
{
	m_iHealth = amt;
	m_iMaxHealth = amt;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CBaseBuildingModel::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
	CTakeDamageInfo newinfo = info;
	// grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit).
	Vector vecDir = vec3_origin;
	if (info.GetInflictor())
	{
		vecDir = info.GetInflictor()->WorldSpaceCenter() - Vector ( 0, 0, 10 ) - WorldSpaceCenter();
		VectorNormalize(vecDir);
	}
	g_vecAttackDir = vecDir;


	if ( m_takedamage == DAMAGE_EVENTS_ONLY )
		return 1;

	// blast damage will get multiplied
	if ( (info.GetDamageType() & DMG_BLAST) )
	{
		newinfo.ScaleDamage( 2.0f );

		float flFractionalDamage = newinfo.GetDamage() - floor( newinfo.GetDamage() );
		float flIntegerDamage = newinfo.GetDamage() - flFractionalDamage;

		if ( flIntegerDamage <= 0 )
			return 0;

		m_iHealth -= flIntegerDamage;

		return 1;
	}

	// bullets hardly do damage
	if ( (info.GetDamageType() & DMG_BULLET) && info.GetDamage() > 1  && m_iHealth > 1 )
	{
		m_iHealth -= 1;
		return 1;
	}

	// default
	// Separate the fractional amount of damage from the whole
	float flFractionalDamage = info.GetDamage() - floor( info.GetDamage() );
	float flIntegerDamage = info.GetDamage() - flFractionalDamage;

	// Add fractional damage to the accumulator
	//m_flDamageAccumulator += flFractionalDamage;

	// If the accumulator is holding a full point of damage, move that point
	// of damage into the damage we're about to inflict.
	/*if( m_flDamageAccumulator >= 1.0 )
	{
		flIntegerDamage += 1.0;
		m_flDamageAccumulator -= 1.0;
	}*/

	if ( flIntegerDamage <= 0 )
		return 0;

	m_iHealth -= flIntegerDamage;

	return 1;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CBaseBuildingModel::OnTakeDamage_Dying( const CTakeDamageInfo &info )
{
	return 1;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CBaseBuildingModel::OnTakeDamage_Dead( const CTakeDamageInfo &info )
{
	// do the damage
	if ( m_takedamage != DAMAGE_EVENTS_ONLY )
	{
		m_iHealth -= info.GetDamage();
	}

	return 1;
}

/*
============
Killed
============
*/
void CBaseBuildingModel::Event_Killed( const CTakeDamageInfo &info )
{
	// remove ourself from the list
	if( g_pRTSResource )
	{
		g_pRTSResource->UnRegisterBuilding( this, m_iOwnerShip );
	}

	if( info.GetAttacker() )
	{
		info.GetAttacker()->Event_KilledOther(this, info);
	}

	m_takedamage = DAMAGE_NO;
	m_lifeState = LIFE_DEAD;

	// now enable nodes/nav areas we were blocking 
	//DisableNodes( false );
	UpdateNavAreas( false );

	DestroyComponents(info);

	// explode
	if( m_iszSoundDestroyed != NULL_STRING )
	{
		EmitSound( STRING(m_iszSoundDestroyed) );
	}

	// make sure we are done with cleaning up before this part.
	Vector vecUp;
	GetVectors( NULL, NULL, &vecUp );
	Vector vecOrigin = WorldSpaceCenter() + ( vecUp * 12.0f );

	if( EF_PARTICLE & m_nExplosionFlags )
	{
		// Our effect
		if( m_iszExplosionParticle != NULL_STRING )
			DispatchParticleEffect( STRING(m_iszExplosionParticle), vecOrigin, GetAbsAngles() );
	}

	if( EF_JUNK & m_nExplosionFlags )
	{
		CPVSFilter filter( vecOrigin );
		for ( int i = 0; i < 4; i++ )
		{
			Vector gibVelocity = RandomVector(-100,100);
			int iModelIndex = modelinfo->GetModelIndex( g_PropDataSystem.GetRandomChunkModel( "MetalChunks" ) );	
			te->BreakModel( filter, 0.0, vecOrigin, GetAbsAngles(), Vector(40,40,40), gibVelocity, iModelIndex, 150, 4, 2.5, BREAK_METAL );
		}
	}

	if( EF_CINEMATIC & m_nExplosionFlags )
	{
		m_fCleanUpTime = gpGlobals->curtime + m_fCleanUpDuration;
		PropSetAnim("explode");
	}

	if( EF_SHAKE & m_nExplosionFlags )
	{
		UTIL_ScreenShake( GetAbsOrigin(), m_Amplitude, m_Frequency, m_Duration, m_Radius, SHAKE_START, true );
	}

	if( EF_EXPLOSION & m_nExplosionFlags )
	{
		if( m_sFireballSprite > 0 )
		{
			Vector origin = GetAbsOrigin();
			CPASFilter filter( origin );
			te->Explosion( filter, 0.0,
				&origin, 
				m_sFireballSprite,
				( m_spriteScale / 10.0 ),
				15,
				TE_EXPLFLAG_NONE,
				( m_iMagnitude * 2.5f ),
				m_iMagnitude );
		}
		else
		{
			Warning("EF_EXPLOSION active, but no valid explosion sprite specified!\n");
		}
	}

	if( EF_SINK & m_nExplosionFlags )
	{
		m_fCleanUpTime = gpGlobals->curtime + m_fCleanUpDuration;

		Vector vAbsMins, vAbsMaxs;
		CollisionProp()->WorldSpaceAABB( &vAbsMins, &vAbsMaxs );
		Vector destination = GetAbsOrigin() + 
			(Vector(0,0,-1) * (vAbsMaxs.z - vAbsMins.z + 32.0f));
		LinearMove( destination, m_fSinkSpeed );
	}					

	// remove
	if( (EF_CINEMATIC & m_nExplosionFlags) || (EF_SINK & m_nExplosionFlags) )
	{
		SetThink( &CBaseBuildingModel::BreakThink );
		SetNextThink( gpGlobals->curtime );
	}
	else
	{
		UTIL_Remove( this );
	}
}

//-----------------------------------------------------------------------------
// Purpose: We are killed and need to break
//-----------------------------------------------------------------------------
void CBaseBuildingModel::BreakThink( void )
{
	if( m_fCleanUpTime < gpGlobals->curtime )
	{
		UTIL_Remove(this);
		return;
	}

	SetNextThink( gpGlobals->curtime + 0.1f );
}

//-----------------------------------------------------------------------------
// Purpose: Calculate m_vecVelocity and m_flNextThink to reach vecDest from
//			GetOrigin() traveling at flSpeed.
// Input  : Vector	vecDest - 
//			flSpeed - 
//-----------------------------------------------------------------------------
void CBaseBuildingModel::LinearMove( const Vector &vecDest, float flSpeed )
{
	ASSERTSZ(flSpeed != 0, "LinearMove:  no speed is defined!");
	
	m_vecFinalDest = vecDest;

	// Already there?
	if (vecDest == GetLocalOrigin())
	{
		MoveDone();
		return;
	}
		
	// set destdelta to the vector needed to move
	Vector vecDestDelta = vecDest - GetLocalOrigin();
	
	// divide vector length by speed to get time to reach dest
	float flTravelTime = vecDestDelta.Length() / flSpeed;

	// set m_flNextThink to trigger a call to LinearMoveDone when dest is reached
	SetMoveDoneTime( flTravelTime );

	// scale the destdelta vector by the time spent traveling to get velocity
	SetLocalVelocity( vecDestDelta / flTravelTime );
}


void CBaseBuildingModel::SetConstructionState( int state )
{
	m_iConstructionState = state;

	if( m_iConstructionState == BS_PRECONSTRUCTION )
	{
		//AddSolidFlags( FSOLID_NOT_SOLID );
		SetSolid( SOLID_NONE );
	}
	else if( m_iConstructionState == BS_UNDERCONSTRUCTION)
	{
		//RemoveSolidFlags( FSOLID_NOT_SOLID );
		SetSolid(SOLID_VPHYSICS );
		m_fConstructFinished = gpGlobals->curtime + m_fConstructTime;
		m_iHealth = 1;	// start construct, interpolate from 1 to maxhealth
		SetThink( &CBaseBuildingModel::ConstructThink );
		SetNextThink( gpGlobals->curtime + 0.1 );
	}
	else
	{
		// add ourselfs to the list
		if(g_pRTSResource)
			g_pRTSResource->RegisterBuilding( this, m_iOwnerShip );

		SetSolid(SOLID_VPHYSICS );
		//RemoveSolidFlags( FSOLID_NOT_SOLID );
		SetThink( &CBaseBuildingModel::BuildThink );
		SetNextThink( gpGlobals->curtime + 0.1 );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseBuildingModel::SetConstructTime( float time )
{
	m_fConstructTime = time;
}

//-----------------------------------------------------------------------------
// Purpose: Constructs the building. When done, make the build think take over.
//-----------------------------------------------------------------------------
void CBaseBuildingModel::ConstructThink( void )
{
	if( m_fConstructFinished <= gpGlobals->curtime )
	{
		SetConstructionState(BS_CONSTRUCTED);
		m_iHealth = m_iMaxHealth;	// make sure we don't have more or less health then we should
	}
	else 
	{
		// calculate progress for client side and increase health with a delta
		float weight = (m_fConstructFinished - gpGlobals->curtime) / m_fConstructTime;
		m_iConstructProgress = 100 - (int)(weight * 100);		// for easy representation client side
		m_iHealth += (int)(m_iMaxHealth / (m_fConstructTime/0.25f));	// depends on the think
		SetNextThink( gpGlobals->curtime + 0.25f );
	}
}

//-----------------------------------------------------------------------------
// Purpose: This is the main loop when constructed
//-----------------------------------------------------------------------------
void CBaseBuildingModel::BuildThink( void )
{
	// update color ownership sprite
	Vector color = g_pRTSResource->GetColor255( GetOwnerShip() );
	CSprite *pSprite = (CSprite *)m_hSprite.Get();
	if ( pSprite )
	{
		pSprite->SetColor( color.x, color.y, color.z );
	}

	SetNextThink( gpGlobals->curtime + 0.5f );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseBuildingModel::CreateComponents( KeyValues *pComponents )
{
	if( !pComponents )
		return;

	Vector temp;
	for ( KeyValues *sub = pComponents->GetFirstSubKey(); sub != NULL ; sub = sub->GetNextKey() )
	{
		CBuildingComponent *pCo = (CBuildingComponent *)CreateEntityByName("rts_buildingcomponent");
		if( !pCo )
			continue;

		pCo->SetOuter(this);

		UTIL_StringToVector( temp.Base(), sub->GetString("origin", "0 0 0") );
		pCo->SetAbsOrigin( GetAbsOrigin() + temp );

		UTIL_StringToVector( temp.Base(), sub->GetString("angles", "0 0 0") );
		pCo->SetAbsAngles( GetAbsAngles() + QAngle(temp.x, temp.y, temp.z) );

		pCo->SetModelName( MAKE_STRING(sub->GetString("model")) );

		KeyValues *keys = sub->FindKey("component_keyvalues");
		if( keys )
			{
			for ( KeyValues *sub2 = keys->GetFirstSubKey(); sub2 != NULL ; sub2 = sub2->GetNextKey() )
			{
				pCo->KeyValue( sub2->GetName(), sub2->GetString() );
			}
		}

		DispatchSpawn(pCo);
		pCo->Activate();

		m_ComponentsList.AddToTail(pCo);
	}
}

//-----------------------------------------------------------------------------
// Purpose: Add a (preplaced) component in map
//-----------------------------------------------------------------------------
void CBaseBuildingModel::AddComponent(CBuildingComponent *pComponent)
{
	if( !pComponent )
		return;

	m_ComponentsList.AddToTail(pComponent);
}

//-----------------------------------------------------------------------------
// Purpose: Precache the components
//-----------------------------------------------------------------------------
void CBaseBuildingModel::PrecacheComponents( KeyValues *pComponents )
{
	if( !pComponents )
		return;

	Vector temp;
	for ( KeyValues *sub = pComponents->GetFirstSubKey(); sub != NULL ; sub = sub->GetNextKey() )
	{
		CBuildingComponent *pCo = (CBuildingComponent *)CreateEntityByName("rts_buildingcomponent");
		if( !pCo )
			continue;

		UTIL_StringToVector( temp.Base(), sub->GetString("origin", "0 0 0") );
		pCo->SetAbsOrigin( GetAbsOrigin() + temp );

		UTIL_StringToVector( temp.Base(), sub->GetString("angles", "0 0 0") );
		pCo->SetAbsAngles( GetAbsAngles() + QAngle(temp.x, temp.y, temp.z) );

		pCo->SetModelName( MAKE_STRING(sub->GetString("model")) );

		KeyValues *keys = sub->FindKey("component_keyvalues");
		if( keys )
			{
			for ( KeyValues *sub2 = keys->GetFirstSubKey(); sub2 != NULL ; sub2 = sub2->GetNextKey() )
			{
				pCo->KeyValue( sub2->GetName(), sub2->GetString() );
			}
		}

		pCo->Precache();

		UTIL_RemoveImmediate( pCo );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseBuildingModel::DestroyComponents( const CTakeDamageInfo &info )
{
	for(int i = 0; i < m_ComponentsList.Count(); i++ )
	{
		if( m_ComponentsList.Element(i) )
		{
			m_ComponentsList.Element(i)->Event_Killed(info);
			m_ComponentsList.Element(i)->SetOuter(NULL);
		}
	}

	m_ComponentsList.Purge();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseBuildingModel::RemoveComponents()
{
	for(int i = 0; i < m_ComponentsList.Count(); i++ )
	{
		if( m_ComponentsList.Element(i) && m_ComponentsList.Element(i)->CanBeRemoved() )
		{
			UTIL_Remove( m_ComponentsList.Element(i));
		}
	}

	m_ComponentsList.Purge();
}

//-----------------------------------------------------------------------------
// Purpose: This doesn't works yet. 
//			dynamic links create dynamic nodes at start of the map.
//-----------------------------------------------------------------------------
bool CBaseBuildingModel::DisableNodes( bool disable )
{
	if( !m_hLinkController.Get() ) 
	{
		Vector vAbsMins, vAbsMaxs;
		vAbsMins = CollisionProp()->OBBMins();
		vAbsMaxs = CollisionProp()->OBBMaxs();

		Msg("(size)min: %f %f %f, max: %f %f %f\n", vAbsMins.x, vAbsMins.y, vAbsMins.z, vAbsMaxs.x ,vAbsMaxs.y, vAbsMaxs.z);

		m_hLinkController = (CAI_DynamicLinkController *)CreateEntityByName( "info_node_link_controller" );
		if( !m_hLinkController.Get() )
		{
			DevMsg(1, "Failed to create node controller for a building!\n");
			return false;
		}
		m_hLinkController.Get()->SetAbsOrigin( GetAbsOrigin() );
		m_hLinkController.Get()->SetAbsAngles( GetAbsAngles() );
		UTIL_SetSize( m_hLinkController.Get(), vAbsMins, vAbsMaxs );
		DispatchSpawn(m_hLinkController.Get());
		m_hLinkController.Get()->Activate();

		m_hLinkController.Get()->CollisionProp()->WorldSpaceAABB( &vAbsMins, &vAbsMaxs );
		Msg("2. min: %f %f %f, max: %f %f %f\n", vAbsMins.x, vAbsMins.y, vAbsMins.z, vAbsMaxs.x ,vAbsMaxs.y, vAbsMaxs.z);

		m_hLinkController.Get()->GenerateLinksFromVolume();

		// --------------------------------------------
		// Initialize any dynamic links
		// --------------------------------------------
		CAI_DynamicLink::InitDynamicLinks();

		m_hLinkController.Get()->GenerateLinksFromVolume();
	}	

	inputdata_t inputdata;
	inputdata.pActivator = this;
	if( disable )
		m_hLinkController.Get()->InputTurnOff( inputdata );
	else 
		m_hLinkController.Get()->InputTurnOn( inputdata );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Finds all nav area's we need to block
//			Unblocks all area's from the blocked list
//-----------------------------------------------------------------------------
#define MAX_AREAS 128
bool CBaseBuildingModel::UpdateNavAreas( bool block )
{
	CNavArea *area;
	// unblock
	if( !block )
	{
		for( int i = 0; i < m_utlBlockedNavList.Count(); i++ )
		{
			area = TheNavMesh->GetNavAreaByID(m_utlBlockedNavList.Element(i));
			if( area )
				area->SetNavBlocked( false );
		}
		m_utlBlockedNavList.RemoveAll();
		return true;
	}

	// block
	// first create a list of all areas covering the building
	CNavArea *pAreaList[MAX_AREAS];
	int num_areas = 0;
	

	Vector vAbsMins, vAbsMaxs;
	Vector vAbsMins2, vAbsMaxs2;

	// use the model render bounds
	Vector vRelMins, vRelMaxs;
	modelinfo->GetModelRenderBounds( GetModel(), vRelMins, vRelMaxs );
	TransformAABB( CollisionProp()->CollisionToWorldTransform(), vRelMins, vRelMaxs, vAbsMins, vAbsMaxs );

	//debugoverlay->AddBoxOverlay( Vector( 0, 0, 0 ), vAbsMins, vAbsMaxs, QAngle( 0, 0, 0 ), 255, 0, 0, 100, 5 );
	//CollisionProp()->WorldSpaceAABB

	/*IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
	if ( pPhysicsObject )
	{
		if ( pPhysicsObject->GetCollide() )
		{
			physcollision->CollideGetAABB( &vAbsMins, &vAbsMaxs, 
				pPhysicsObject->GetCollide(), CollisionProp()->GetCollisionOrigin(), CollisionProp()->GetCollisionAngles() );
		}
	}*/

	// transform into world
	//vAbsMins += GetAbsOrigin();
	//vAbsMaxs += GetAbsOrigin();
		
	vAbsMins.z -= 64.0f;		// make sure the building box will intersect the ground

	FOR_EACH_LL( TheNavAreaList, it )
	{
		area = TheNavAreaList[ it ];

		vAbsMins2 = area->GetExtent().lo;
		vAbsMaxs2 = area->GetExtent().hi;

		// seems the extents are only 2d valid hi and lo
		if( vAbsMins2[2] > vAbsMaxs2[2] )
		{
			float z = vAbsMaxs2[2];
			vAbsMaxs2[2] = vAbsMins2[2];
			vAbsMins2[2] = z;
		}
		else if( vAbsMins2[2] == vAbsMaxs2[2] )
		{
			vAbsMaxs2[2] += 1.0f;
		}

		// does it intersects?
		if( IsBoxIntersectingBox( vAbsMins, vAbsMaxs, 
			vAbsMins2, vAbsMaxs2 ) )
		{
			pAreaList[num_areas++] = area;

			if( num_areas > MAX_AREAS-1 )
				break;
		}
	}

	// now split them
	for( int i = 0; i < num_areas; i++ )
	{
		area = pAreaList[i];
		
		// First split up into areas that cover the building
		area->SplitEdit( false, TheNavMesh->SnapToGrid(vAbsMins.x, true), NULL, &area );
		TheNavMesh->StripNavigationAreas();

		area->SplitEdit( false, TheNavMesh->SnapToGrid(vAbsMaxs.x, true), &area );
		TheNavMesh->StripNavigationAreas();

		area->SplitEdit( true, TheNavMesh->SnapToGrid(vAbsMins.y, true), NULL, &area );
		TheNavMesh->StripNavigationAreas();

		area->SplitEdit( true, TheNavMesh->SnapToGrid(vAbsMaxs.y, true), &area );
		TheNavMesh->StripNavigationAreas();

		// The last area we get is the one we want to block
		// Add the area to the blocked list so we can enable it again when we are killed
		area->SetNavBlocked(true);
		m_utlBlockedNavList.AddToTail(area->GetID() );
	}

	return true;
}
//--- anim stuff
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CBaseBuildingModel::AnimThink( void )
{
	if ( m_nPendingSequence != -1 )
	{
		FinishSetSequence( m_nPendingSequence );
		m_nPendingSequence = -1;
	}

	if ( m_bRandomAnimator && m_flNextRandAnim < gpGlobals->curtime )
	{
		ResetSequence( SelectWeightedSequence( ACT_IDLE ) );
		ResetClientsideFrame();

		m_flNextRandAnim = gpGlobals->curtime + random->RandomFloat( m_flMinRandAnimTime, m_flMaxRandAnimTime );
	}

	if ( ((m_iTransitionDirection > 0 && GetCycle() >= 0.999f) || (m_iTransitionDirection < 0 && GetCycle() <= 0.0f)) && !SequenceLoops() )
	{
		Assert( m_iGoalSequence >= 0 );
		if (GetSequence() != m_iGoalSequence)
		{
			PropSetSequence( m_iGoalSequence );
		}
		else
		{
			// If I'm a random animator, think again when it's time to change sequence
			if ( m_bRandomAnimator )
			{
				SetContextThink( &CBaseBuildingModel::AnimThink, gpGlobals->curtime + m_flNextRandAnim + 0.1f, BUILDING_ANIM );
			}
			else 
			{
				if (m_iszIdleAnim != NULL_STRING)
				{
					PropSetAnim( STRING( m_iszIdleAnim ) );
				}	
			}
		}
	}
	else
	{
		SetContextThink( &CBaseBuildingModel::AnimThink, gpGlobals->curtime + 0.1f, BUILDING_ANIM );
	}

	StudioFrameAdvance();
	DispatchAnimEvents(this);
}


//------------------------------------------------------------------------------
// Purpose: Sets an animation by sequence name or activity name.
//------------------------------------------------------------------------------
void CBaseBuildingModel::PropSetAnim( const char *szAnim )
{
	if ( !szAnim )
		return;

	int nSequence = LookupSequence( szAnim );

	// Set to the desired anim, or default anim if the desired is not present
	if ( nSequence > ACTIVITY_NOT_AVAILABLE )
	{
		PropSetSequence( nSequence );
	}
	else
	{
		SetSequence( 0 );
	}
}
//-----------------------------------------------------------------------------
// Purpose: Helper in case we have to async load the sequence
// Input  : nSequence - 
//-----------------------------------------------------------------------------
void CBaseBuildingModel::FinishSetSequence( int nSequence )
{
	// Msg("%.2f CDynamicProp::FinishSetSequence( %d )\n", gpGlobals->curtime, nSequence );
	SetCycle( 0 );
	m_flAnimTime = gpGlobals->curtime;
	ResetSequence( nSequence );
	ResetClientsideFrame();
	SetPlaybackRate( m_iTransitionDirection > 0 ? 1.0f : -1.0f );
	SetCycle( m_iTransitionDirection > 0 ? 0.0f : 0.999f );
}

//-----------------------------------------------------------------------------
// Purpose: Sets the sequence and starts thinking.
// Input  : nSequence - 
//-----------------------------------------------------------------------------
void CBaseBuildingModel::PropSetSequence( int nSequence )
{
	m_iGoalSequence = nSequence;

	// Msg("%.2f CDynamicProp::PropSetSequence( %d (%d:%.1f:%.3f)\n", gpGlobals->curtime, nSequence, GetSequence(), GetPlaybackRate(), GetCycle() );

	int nNextSequence;
	float nextCycle;
	float flInterval = 0.1f;

	if (GotoSequence( GetSequence(), GetCycle(), GetPlaybackRate(), m_iGoalSequence, nNextSequence, nextCycle, m_iTransitionDirection ))
	{
		FinishSetSequence( nNextSequence );
	}

	SetContextThink( &CBaseBuildingModel::AnimThink, gpGlobals->curtime + flInterval, BUILDING_ANIM );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// CBuildingComponent
// Purpose: A way to add submodels that can receive clicks or damage and pass it
//			to the parent building
//-----------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( rts_buildingcomponent, CBuildingComponent );

BEGIN_DATADESC( CBuildingComponent )
	// function pointers
	DEFINE_THINKFUNC( BreakThink ),
	DEFINE_THINKFUNC( AnimThink ),

	// explosion fields
	DEFINE_FIELD( m_nExplosionFlags,		FIELD_INTEGER ),
	DEFINE_FIELD( m_iszExplosionParticle,	FIELD_STRING ),
	DEFINE_FIELD( m_iszFireballSprite,		FIELD_STRING ),
	DEFINE_FIELD( m_sFireballSprite,		FIELD_SHORT ),
	DEFINE_FIELD( m_iMagnitude,				FIELD_INTEGER ),
	DEFINE_FIELD( m_spriteScale,			FIELD_INTEGER ),
	DEFINE_FIELD( m_fCleanUpDuration,		FIELD_FLOAT ),
	DEFINE_FIELD( m_fCleanUpTime,			FIELD_TIME ),
	DEFINE_FIELD( m_Amplitude,				FIELD_FLOAT ),
	DEFINE_FIELD( m_Frequency,				FIELD_FLOAT ),
	DEFINE_FIELD( m_Duration,				FIELD_FLOAT ),
	DEFINE_FIELD( m_Radius,					FIELD_FLOAT ),
END_DATADESC()

IMPLEMENT_SERVERCLASS_ST( CBuildingComponent, DT_BuildingComponent )
	SendPropEHandle		( SENDINFO( m_hOuter)),
END_SEND_TABLE()

//-----------------------------------------------------------------------------
CBuildingComponent::CBuildingComponent()
{
	m_takedamage = DAMAGE_YES;
	m_bCanBeRemoved = true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
Class_T	CBuildingComponent::Classify()
{
	return CLASS_BUILDING;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CBuildingComponent::OnTakeDamage( const CTakeDamageInfo &info )
{
	if( m_takedamage != DAMAGE_YES )
		return 0;

	if( GetOuter() )
		return GetOuter()->OnTakeDamage(info);

	return 0;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBuildingComponent::Precache()
{
	if( m_iszExplosionParticle != NULL_STRING )
		PrecacheParticleSystem(STRING(m_iszExplosionParticle));

	if ( m_iszFireballSprite != NULL_STRING )
		m_sFireballSprite = PrecacheModel( STRING( m_iszFireballSprite ) );

	BaseClass::Precache();
}

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

	if( GetParent() )
	{
		CBaseBuildingModel *pParent = dynamic_cast<CBaseBuildingModel *>(GetParent());
		if( !pParent )
		{
			Warning("rts_buildingcomponent with none basebuilding type parent\n");
			return;
		}

		pParent->AddComponent(this);
		SetOuter(pParent);
		SetParent(NULL);	// shouldn't move with placable tank. Could do this nicer?
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CBuildingComponent::KeyValue( const char *szKeyName, const char *szValue )
{
	if ( FStrEq( szKeyName, "damage_type" ) )
	{
		m_takedamage = atoi(szValue);
		return true;
	}

	if( FStrEq( szKeyName, "solid" ) )
	{
		SetSolid( (SolidType_t)atoi(szValue) );
	}

	// explosion settings
	if ( FStrEq( szKeyName, "explosion_flags" ) )
	{
		m_nExplosionFlags = atoi(szValue);
		return true;
	}

	// EF_EXPLOSION
	if ( FStrEq( szKeyName, "explosion_sprite" ) )
	{
		m_iszFireballSprite = AllocPooledString(szValue);
		return true;
	}

	if ( FStrEq( szKeyName, "explosion_magnitude" ) )
	{
		m_iMagnitude = atoi(szValue);
		m_spriteScale = (int)((m_iMagnitude - 50) * 0.6);
		return true;
	}

	// EF_PARTICLE
	if ( FStrEq( szKeyName, "explosion_particle" ) )
	{
		m_iszExplosionParticle = AllocPooledString(szValue);
		return true;
	}
	
	// EF_CINEMATIC
	if( FStrEq( szKeyName, "explosion_cleanupdelay" ) )
	{
		m_fCleanUpDuration = atof(szValue);
	}

	// EF_SHAKE
	if( FStrEq( szKeyName, "explosion_shake_radius" ) )
	{
		m_Radius = atof(szValue);
	}

	if( FStrEq( szKeyName, "explosion_shake_amplitude" ) )
	{
		m_Amplitude = atof(szValue);
	}

	if( FStrEq( szKeyName, "explosion_shake_duration" ) )
	{
		m_Duration = atof(szValue);
	}

	if( FStrEq( szKeyName, "explosion_shake_frequency" ) )
	{
		m_Frequency = atof(szValue);
	}

	return BaseClass::KeyValue( szKeyName, szValue );
}

//-----------------------------------------------------------------------------
// Purpose: Being called on right click
//-----------------------------------------------------------------------------
void CBuildingComponent::OnClickRight( CBasePlayer *pActivator ) 
{
	if( GetOuter() )
		GetOuter()->OnClickRight(pActivator);
}

//-----------------------------------------------------------------------------
// Purpose: Being called on right double click
//-----------------------------------------------------------------------------
void CBuildingComponent::OnClickRightDouble( CBasePlayer *pActivator ) 
{
	if( GetOuter() )
		GetOuter()->OnClickRightDouble(pActivator);
}


//-----------------------------------------------------------------------------
// Purpose: Left click
//-----------------------------------------------------------------------------
void CBuildingComponent::OnClickLeft( CBasePlayer *pActivator ) 
{
	if( GetOuter() )
		GetOuter()->OnClickLeft(pActivator);
}

//-----------------------------------------------------------------------------
// Purpose: Left click double
//-----------------------------------------------------------------------------
void CBuildingComponent::OnClickLeftDouble( CBasePlayer *pActivator ) 
{
	if( GetOuter() )
		GetOuter()->OnClickLeftDouble(pActivator);
}

/*
============
Killed
============
*/
void CBuildingComponent::Event_Killed( const CTakeDamageInfo &info )
{
	m_takedamage = DAMAGE_NO;

	// explode
	// make sure we are done with cleaning up before this part.
	Vector vecUp;
	GetVectors( NULL, NULL, &vecUp );
	Vector vecOrigin = WorldSpaceCenter() + ( vecUp * 12.0f );

	if( EF_PARTICLE & m_nExplosionFlags )
	{
		// Our effect
		if( m_iszExplosionParticle != NULL_STRING )
			DispatchParticleEffect( STRING(m_iszExplosionParticle), vecOrigin, GetAbsAngles() );
	}

	if( EF_JUNK & m_nExplosionFlags )
	{
		CPVSFilter filter( vecOrigin );
		for ( int i = 0; i < 4; i++ )
		{
			Vector gibVelocity = RandomVector(-100,100);
			int iModelIndex = modelinfo->GetModelIndex( g_PropDataSystem.GetRandomChunkModel( "MetalChunks" ) );	
			te->BreakModel( filter, 0.0, vecOrigin, GetAbsAngles(), Vector(40,40,40), gibVelocity, iModelIndex, 150, 4, 2.5, BREAK_METAL );
		}
	}

	if( EF_CINEMATIC & m_nExplosionFlags )
	{
		m_fCleanUpTime = gpGlobals->curtime + m_fCleanUpDuration;
		PropSetAnim("explode");
	}

	if( EF_SHAKE & m_nExplosionFlags )
	{
		UTIL_ScreenShake( GetAbsOrigin(), m_Amplitude, m_Frequency, m_Duration, m_Radius, SHAKE_START, true );
	}

	if( EF_EXPLOSION & m_nExplosionFlags )
	{
		if( m_sFireballSprite > 0 )
		{
			CPASFilter filter( vecOrigin );
			te->Explosion( filter, 0.0,
				&vecOrigin, 
				m_sFireballSprite,
				( m_spriteScale / 10.0 ),
				15,
				TE_EXPLFLAG_NONE,
				( m_iMagnitude * 2.5f ),
				m_iMagnitude );
		}
		else
		{
			Warning("EF_EXPLOSION active, but no valid explosion sprite specified!\n");
		}
	}

	if( EF_SINK & m_nExplosionFlags )
	{
		m_fCleanUpTime = gpGlobals->curtime + m_fCleanUpDuration;

		Vector vAbsMins, vAbsMaxs;
		CollisionProp()->WorldSpaceAABB( &vAbsMins, &vAbsMaxs );
		Vector destination = GetAbsOrigin() + 
			(Vector(0,0,-1) * (vAbsMaxs.z - vAbsMins.z + 32.0f));
		LinearMove( destination, m_fSinkSpeed );
	}

	// remove
	if( (EF_CINEMATIC & m_nExplosionFlags) || (EF_SINK & m_nExplosionFlags) )
	{
		m_bCanBeRemoved = false;
		SetThink( &CBuildingComponent::BreakThink );
		SetNextThink( gpGlobals->curtime + 0.1f );
	}
	else
	{
		UTIL_Remove( this );
	}
}

//-----------------------------------------------------------------------------
// Purpose: We are killed and need to break
//-----------------------------------------------------------------------------
void CBuildingComponent::BreakThink( void )
{
	if( m_fCleanUpTime < gpGlobals->curtime )
	{
		UTIL_Remove(this);
		return;
	}

	SetNextThink( gpGlobals->curtime + 0.1f );
}

//-----------------------------------------------------------------------------
// Purpose: Calculate m_vecVelocity and m_flNextThink to reach vecDest from
//			GetOrigin() traveling at flSpeed.
// Input  : Vector	vecDest - 
//			flSpeed - 
//-----------------------------------------------------------------------------
void CBuildingComponent::LinearMove( const Vector &vecDest, float flSpeed )
{
	ASSERTSZ(flSpeed != 0, "LinearMove:  no speed is defined!");
	
	m_vecFinalDest = vecDest;

	// Already there?
	if (vecDest == GetLocalOrigin())
	{
		MoveDone();
		return;
	}
		
	// set destdelta to the vector needed to move
	Vector vecDestDelta = vecDest - GetLocalOrigin();
	
	// divide vector length by speed to get time to reach dest
	float flTravelTime = vecDestDelta.Length() / flSpeed;

	// set m_flNextThink to trigger a call to LinearMoveDone when dest is reached
	SetMoveDoneTime( flTravelTime );

	// scale the destdelta vector by the time spent traveling to get velocity
	SetLocalVelocity( vecDestDelta / flTravelTime );
}

//--- anim stuff
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CBuildingComponent::AnimThink( void )
{
	if ( m_nPendingSequence != -1 )
	{
		FinishSetSequence( m_nPendingSequence );
		m_nPendingSequence = -1;
	}

	if ( m_bRandomAnimator && m_flNextRandAnim < gpGlobals->curtime )
	{
		ResetSequence( SelectWeightedSequence( ACT_IDLE ) );
		ResetClientsideFrame();

		m_flNextRandAnim = gpGlobals->curtime + random->RandomFloat( m_flMinRandAnimTime, m_flMaxRandAnimTime );
	}

	if ( ((m_iTransitionDirection > 0 && GetCycle() >= 0.999f) || (m_iTransitionDirection < 0 && GetCycle() <= 0.0f)) && !SequenceLoops() )
	{
		Assert( m_iGoalSequence >= 0 );
		if (GetSequence() != m_iGoalSequence)
		{
			PropSetSequence( m_iGoalSequence );
		}
		else
		{
			// If I'm a random animator, think again when it's time to change sequence
			if ( m_bRandomAnimator )
			{
				SetContextThink( &CBuildingComponent::AnimThink, gpGlobals->curtime + m_flNextRandAnim + 0.1f, BUILDING_ANIM );
			}
		}
	}
	else
	{
		SetContextThink( &CBuildingComponent::AnimThink, gpGlobals->curtime + 0.1f, BUILDING_ANIM );
	}

	StudioFrameAdvance();
	DispatchAnimEvents(this);
}


//------------------------------------------------------------------------------
// Purpose: Sets an animation by sequence name or activity name.
//------------------------------------------------------------------------------
void CBuildingComponent::PropSetAnim( const char *szAnim )
{
	if ( !szAnim )
		return;

	int nSequence = LookupSequence( szAnim );

	// Set to the desired anim, or default anim if the desired is not present
	if ( nSequence > ACTIVITY_NOT_AVAILABLE )
	{
		PropSetSequence( nSequence );
	}
	else
	{
		SetSequence( 0 );
	}
}
//-----------------------------------------------------------------------------
// Purpose: Helper in case we have to async load the sequence
// Input  : nSequence - 
//-----------------------------------------------------------------------------
void CBuildingComponent::FinishSetSequence( int nSequence )
{
	// Msg("%.2f CDynamicProp::FinishSetSequence( %d )\n", gpGlobals->curtime, nSequence );
	SetCycle( 0 );
	m_flAnimTime = gpGlobals->curtime;
	ResetSequence( nSequence );
	ResetClientsideFrame();
	SetPlaybackRate( m_iTransitionDirection > 0 ? 1.0f : -1.0f );
	SetCycle( m_iTransitionDirection > 0 ? 0.0f : 0.999f );
}

//-----------------------------------------------------------------------------
// Purpose: Sets the sequence and starts thinking.
// Input  : nSequence - 
//-----------------------------------------------------------------------------
void CBuildingComponent::PropSetSequence( int nSequence )
{
	m_iGoalSequence = nSequence;

	// Msg("%.2f CDynamicProp::PropSetSequence( %d (%d:%.1f:%.3f)\n", gpGlobals->curtime, nSequence, GetSequence(), GetPlaybackRate(), GetCycle() );

	int nNextSequence;
	float nextCycle;
	float flInterval = 0.1f;

	if (GotoSequence( GetSequence(), GetCycle(), GetPlaybackRate(), m_iGoalSequence, nNextSequence, nextCycle, m_iTransitionDirection ))
	{
		FinishSetSequence( nNextSequence );
	}

	SetContextThink( &CBuildingComponent::AnimThink, gpGlobals->curtime + flInterval, BUILDING_ANIM );
}