//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Implements breakables and pushables. func_breakable is a bmodel
//			that breaks into pieces after taking damage.
//
//=============================================================================//

#include "cbase.h"
#include "player.h"
#include "filters.h"
#include "func_break.h"
#include "decals.h"
#include "explode.h"
#include "in_buttons.h"
#include "physics.h"
#include "IEffects.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"
#include "globals.h"
#include "util.h"
#include "physics_impact_damage.h"
#include "vstdlib/ICommandLine.h"

#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out
#include "bot/bot.h"
#endif

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

ConVar func_break_max_pieces( "func_break_max_pieces", "15", FCVAR_ARCHIVE | FCVAR_REPLICATED );

#ifdef HL1_DLL
extern void PlayerPickupObject( CBasePlayer *pPlayer, CBaseEntity *pObject );
#endif

extern Vector		g_vecAttackDir;

// Just add more items to the bottom of this array and they will automagically be supported
// This is done instead of just a classname in the FGD so we can control which entities can
// be spawned, and still remain fairly flexible

#ifndef HL1_DLL
	const char *CBreakable::pSpawnObjects[] =
	{
		NULL,						// 0
		"item_battery",				// 1
		"item_healthkit",			// 2
		"item_ammo_pistol",			// 3
		"item_ammo_pistol_large",	// 4
		"item_ammo_smg1",			// 5
		"item_ammo_smg1_large",		// 6
		"item_ammo_ar2",			// 7
		"item_ammo_ar2_large",		// 8
		"item_box_buckshot",		// 9
		"item_flare_round",			// 10
		"item_box_flare_rounds",	// 11
		"item_rpg_round",			// 12
		"unused (item_smg1_grenade) 13",// 13
		"item_box_sniper_rounds",	// 14
		"unused (???) 15",			// 15
		"weapon_stunstick",			// 16
		"unused (weapon_ar1) 17",	// 17
		"weapon_ar2",				// 18
		"unused (???) 19",				// 19
		"weapon_rpg",				// 20
		"weapon_smg1",				// 21
		"unused (weapon_smg2) 22",	// 22
		"unused (weapon_slam) 23",	// 23
		"weapon_shotgun",			// 24
		"unused (weapon_molotov) 25",// 25
		"item_dynamic_resupply",	// 26
	};
#else
	// Half-Life 1 spawn objects!
	const char *CBreakable::pSpawnObjects[] =
	{
		NULL,				// 0
		"item_battery",		// 1
		"item_healthkit",	// 2
		"weapon_glock",		// 3
		"ammo_9mmclip",		// 4
		"weapon_mp5",		// 5
		"ammo_9mmAR",		// 6
		"ammo_ARgrenades",	// 7
		"weapon_shotgun",	// 8
		"ammo_buckshot",	// 9
		"weapon_crossbow",	// 10
		"ammo_crossbow",	// 11
		"weapon_357",		// 12
		"ammo_357",			// 13
		"weapon_rpg",		// 14
		"ammo_rpgclip",		// 15
		"ammo_gaussclip",	// 16
		"weapon_handgrenade",// 17
		"weapon_tripmine",	// 18
		"weapon_satchel",	// 19
		"weapon_snark",		// 20
		"weapon_hornetgun",	// 21
	};
#endif

const char *pFGDPropData[] =
{
	NULL,
	"Wooden.Tiny",
	"Wooden.Small",
	"Wooden.Medium",
	"Wooden.Large",
	"Wooden.Huge",
	"Metal.Small",
	"Metal.Medium",
	"Metal.Large",
	"Cardboard.Small",
	"Cardboard.Medium",
	"Cardboard.Large",
	"Stone.Small",
	"Stone.Medium",
	"Stone.Large",
	"Stone.Huge",
	"Glass.Small",
	"Plastic.Small",
	"Plastic.Medium",
	"Plastic.Large",
	"Pottery.Small",
	"Pottery.Medium",
	"Pottery.Large",
	"Pottery.Huge",
	"Glass.Window",
};

LINK_ENTITY_TO_CLASS( func_breakable, CBreakable );
BEGIN_DATADESC( CBreakable )

	DEFINE_FIELD( m_Material, FIELD_INTEGER ),
	DEFINE_KEYFIELD( m_Explosion, FIELD_INTEGER, "explosion" ),
	DEFINE_KEYFIELD( m_GibDir, FIELD_VECTOR, "gibdir" ),
	DEFINE_FIELD( m_hBreaker, FIELD_EHANDLE ),

	// Don't need to save/restore these because we precache after restore
	//DEFINE_FIELD( m_idShard, FIELD_INTEGER ),
	DEFINE_FIELD( m_angle, FIELD_FLOAT ),
	DEFINE_FIELD( m_iszGibModel, FIELD_STRING ),
	DEFINE_FIELD( m_iszSpawnObject, FIELD_STRING ),
	DEFINE_KEYFIELD( m_ExplosionMagnitude, FIELD_INTEGER, "explodemagnitude" ),
	DEFINE_KEYFIELD( m_flPressureDelay, FIELD_FLOAT, "PressureDelay" ),
	DEFINE_KEYFIELD( m_iMinHealthDmg, FIELD_INTEGER, "minhealthdmg" ),
	DEFINE_FIELD( m_bTookPhysicsDamage, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_iszPropData, FIELD_STRING ),
	DEFINE_INPUT( m_impactEnergyScale, FIELD_FLOAT, "physdamagescale" ),

	DEFINE_INPUTFUNC( FIELD_VOID, "Break", InputBreak ),
	DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ),
	DEFINE_INPUTFUNC( FIELD_INTEGER, "AddHealth", InputAddHealth ),
	DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveHealth", InputRemoveHealth ),

	// Function Pointers
	DEFINE_ENTITYFUNC( BreakTouch ),
	DEFINE_THINKFUNC( Die ),

	// Outputs
	DEFINE_OUTPUT(m_OnBreak, "OnBreak"),
	DEFINE_OUTPUT(m_OnHealthChanged, "OnHealthChanged"),

	DEFINE_FIELD( m_flDmgModBullet, FIELD_FLOAT ),
	DEFINE_FIELD( m_flDmgModClub, FIELD_FLOAT ),
	DEFINE_FIELD( m_flDmgModExplosive, FIELD_FLOAT ),
	DEFINE_FIELD( m_iszPhysicsDamageTableName, FIELD_STRING ),
	DEFINE_FIELD( m_iszBreakableModel, FIELD_STRING ),
	DEFINE_FIELD( m_iBreakableSkin, FIELD_INTEGER ),
	DEFINE_FIELD( m_iBreakableCount, FIELD_INTEGER ),
	DEFINE_FIELD( m_iMaxBreakableSize, FIELD_INTEGER ),
	DEFINE_FIELD( m_iszBasePropData, FIELD_STRING ),
	DEFINE_FIELD( m_iInteractions, FIELD_INTEGER ),
	DEFINE_FIELD( m_explodeRadius, FIELD_FLOAT ),
	DEFINE_FIELD( m_iszModelName, FIELD_STRING ),
	
	// Physics Influence
	DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ),
	DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ),

END_DATADESC()

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBreakable::KeyValue( const char *szKeyName, const char *szValue )
{
	// UNDONE_WC: explicitly ignoring these fields, but they shouldn't be in the map file!
	if (FStrEq(szKeyName, "material"))
	{
		int i = atoi( szValue);

		// 0:glass, 1:metal, 2:flesh, 3:wood

		if ((i < 0) || (i >= matLastMaterial))
			m_Material = matWood;
		else
			m_Material = (Materials)i;
	}
	else if (FStrEq(szKeyName, "deadmodel"))
	{
	}
	else if (FStrEq(szKeyName, "shards"))
	{
//			m_iShards = atof(szValue);
	}
	else if (FStrEq(szKeyName, "gibmodel") )
	{
		m_iszGibModel = AllocPooledString(szValue);
	}
	else if (FStrEq(szKeyName, "spawnobject") )
	{
		int object = atoi( szValue );
		if ( object > 0 && object < ARRAYSIZE(pSpawnObjects) )
			m_iszSpawnObject = MAKE_STRING( pSpawnObjects[object] );
	}
	else if (FStrEq(szKeyName, "propdata") )
	{
		int pdata = atoi( szValue );
		if ( pdata > 0 && pdata < ARRAYSIZE(pFGDPropData) )
		{
			m_iszPropData = MAKE_STRING( pFGDPropData[pdata] );
		}
		else if ( pdata )
		{
			// If you've hit this warning, it's probably because someone's added a new
			// propdata field to func_breakables in the .fgd, and not added it to the
			// pFGDPropData list.
			Warning("func_breakable with invalid propdata %d.\n", pdata );
		}
	}
	else if (FStrEq(szKeyName, "lip") )
	{
	}
	else
		return BaseClass::KeyValue( szKeyName, szValue );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBreakable::Spawn( void )
{
	// Initialize damage modifiers. Must be done before baseclass spawn.
	m_flDmgModBullet = func_breakdmg_bullet.GetFloat();
	m_flDmgModClub = func_breakdmg_club.GetFloat();
	m_flDmgModExplosive = func_breakdmg_explosive.GetFloat();

	ParsePropData();

    Precache( );    

	if ( !m_iHealth || FBitSet( m_spawnflags, SF_BREAK_TRIGGER_ONLY ) )
	{
		// This allows people to shoot at the glass (since it's penetrable)
		if ( m_Material == matGlass )
		{
			m_iHealth = 1;
		}

		m_takedamage = DAMAGE_NO;
	}
	else
	{
		m_takedamage = DAMAGE_YES;
	}

	m_iMaxHealth = ( m_iHealth > 0 ) ? m_iHealth : 1;
  
	SetSolid( SOLID_BSP );
    SetMoveType( MOVETYPE_PUSH );
	
	// this is a hack to shoot the gibs in a specific yaw/direction
	m_angle = GetLocalAngles().y;
	SetLocalAngles( vec3_angle );
	
	SetModel( STRING( GetModelName() ) );//set size and link into world.

	SetTouch( &CBreakable::BreakTouch );
	if ( FBitSet( m_spawnflags, SF_BREAK_TRIGGER_ONLY ) )		// Only break on trigger
	{
		SetTouch( NULL );
	}

	// Flag unbreakable glass as "worldbrush" so it will block ALL tracelines
	if ( !IsBreakable() && m_nRenderMode != kRenderNormal )
		AddFlag( FL_WORLDBRUSH );

	if ( m_impactEnergyScale == 0 )
	{
		m_impactEnergyScale = 1.0;
	}

	CreateVPhysics();
}

//-----------------------------------------------------------------------------
// Purpose: Parse this prop's data, if it has a keyvalues section.
//			Returns true only if this prop is using a model that has a prop_data section that's invalid.
//-----------------------------------------------------------------------------
void CBreakable::ParsePropData( void )
{
	if ( m_iszPropData == NULL_STRING )
		return;

	if ( !Q_strncmp( STRING(m_iszPropData), "None", 4 ) )
		return;

	g_PropDataSystem.ParsePropFromBase( this, STRING(m_iszPropData) );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBreakable::CreateVPhysics( void )
{
	VPhysicsInitStatic();
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
const char *CBreakable::MaterialSound( Materials precacheMaterial )
{
    switch ( precacheMaterial ) 
	{
	case matWood:
		return "Breakable.MatWood";
	case matFlesh:
	case matWeb:
		return "Breakable.MatFlesh";
	case matComputer:
		return "Breakable.Computer";
	case matUnbreakableGlass:
	case matGlass:
		return "Breakable.MatGlass";
	case matMetal:
		return "Breakable.MatMetal";
	case matCinderBlock:
	case matRocks:
		return "Breakable.MatConcrete";
	case matCeilingTile:
	case matNone:
	default:
		break;
	}

	return NULL;
}


void CBreakable::MaterialSoundRandom( int entindex, Materials soundMaterial, float volume )
{
	const char	*soundname;
	soundname = MaterialSound( soundMaterial );
	if ( !soundname )
		return;

	CSoundParameters params;
	if ( !GetParametersForSound( soundname, params, NULL ) )
		return;

	CPASAttenuationFilter filter( CBaseEntity::Instance( entindex ), params.soundlevel );


	EmitSound_t ep;
	ep.m_nChannel = params.channel;
	ep.m_pSoundName = params.soundname;
	ep.m_flVolume = volume;
	ep.m_SoundLevel = params.soundlevel;

	EmitSound( filter, entindex, ep );
}


void CBreakable::Precache( void )
{
	const char *pGibName = "WoodChunks";

    switch (m_Material) 
	{
	case matWood:
		pGibName = "WoodChunks";
		break;

	case matUnbreakableGlass:
	case matGlass:
		pGibName = "GlassChunks";
		break;

	case matMetal:
		pGibName = "MetalChunks";
		break;

	case matRocks:
		pGibName = "ConcreteChunks";
		break;

#ifdef HL1_DLL
	case matComputer:
		pGibName = "ComputerGibs";
		break;

	case matCeilingTile:
		pGibName = "CeilingTile";
		break;

	case matFlesh:
		pGibName = "FleshGibs";
		break;

	case matCinderBlock:
		pGibName = "CinderBlocks";
		break;

	case matWeb:
		pGibName = "WebGibs";
		break;
#else

	case matCinderBlock:
		pGibName = "ConcreteChunks";
		break;
#endif

	default:
		Warning("%s (%s) at (%.3f %.3f %.3f) using obsolete or unknown material type.\n", GetClassname(), GetDebugName(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
		pGibName = "WoodChunks";
		break;
	}

	if ( m_iszGibModel != NULL_STRING )
	{
		pGibName = STRING(m_iszGibModel);

#ifdef HL1_DLL
		PrecacheModel( pGibName );
#endif
	}

	m_iszModelName = MAKE_STRING( pGibName );

	// Precache the spawn item's data
	if ( !CommandLine()->CheckParm("-makereslists"))
	{
		if ( m_iszSpawnObject != NULL_STRING )
		{
			UTIL_PrecacheOther( STRING( m_iszSpawnObject ) );
		}
	}
	else
	{
		// Actually, precache all possible objects...
		for ( int i = 0; i < ARRAYSIZE(pSpawnObjects) ; ++i )
		{
			if ( !pSpawnObjects[ i ] )
				continue;

			if ( !Q_strnicmp( pSpawnObjects[ i ], "unused", Q_strlen( "unused" ) ) )
				continue;

			UTIL_PrecacheOther( pSpawnObjects[ i ] );
		}	
	}

	PrecacheScriptSound( "Breakable.MatGlass" );
	PrecacheScriptSound( "Breakable.MatWood" );
	PrecacheScriptSound( "Breakable.MatMetal" );
	PrecacheScriptSound( "Breakable.MatFlesh" );
	PrecacheScriptSound( "Breakable.MatConcrete" );
	PrecacheScriptSound( "Breakable.Computer" );
	PrecacheScriptSound( "Breakable.Crate" );
	PrecacheScriptSound( "Breakable.Glass" );
	PrecacheScriptSound( "Breakable.Metal" );
	PrecacheScriptSound( "Breakable.Flesh" );
	PrecacheScriptSound( "Breakable.Concrete" );
	PrecacheScriptSound( "Breakable.Ceiling" );
}

// play shard sound when func_breakable takes damage.
// the more damage, the louder the shard sound.


void CBreakable::DamageSound( void )
{
	int pitch;
	float fvol;
	char *soundname = NULL;
	int material = m_Material;

	if (random->RandomInt(0,2))
	{
		pitch = PITCH_NORM;
	}
	else
	{
		pitch = 95 + random->RandomInt(0,34);
	}

	fvol = random->RandomFloat(0.75, 1.0);

	if (material == matComputer && random->RandomInt(0,1))
	{
		material = matMetal;
	}

	switch (material)
	{
	case matGlass:
	case matUnbreakableGlass:
		soundname = "Breakable.MatGlass";
		break;

	case matWood:
		soundname = "Breakable.MatWood";
		break;

	case matMetal:
		soundname = "Breakable.MatMetal";
		break;

	case matRocks:
	case matCinderBlock:
		soundname = "Breakable.MatConcrete";
		break;

	case matComputer:
		soundname = "Breakable.Computer";
		break;

	default:
		break;
	}

	if ( soundname )
	{
		CSoundParameters params;
		if ( GetParametersForSound( soundname, params, NULL ) )
		{
			CPASAttenuationFilter filter( this );

			EmitSound_t ep;
			ep.m_nChannel = params.channel;
			ep.m_pSoundName = params.soundname;
			ep.m_flVolume = fvol;
			ep.m_SoundLevel = params.soundlevel;
			ep.m_nPitch = pitch;

			EmitSound( filter, entindex(), ep );
		}
	}
}

void CBreakable::BreakTouch( CBaseEntity *pOther )
{
	float flDamage;
	
	// only players can break these right now
	if ( !pOther->IsPlayer() || !IsBreakable() )
	{
        return;
	}

	// can I be broken when run into?
	if ( HasSpawnFlags( SF_BREAK_TOUCH ) )
	{
		flDamage = pOther->GetSmoothedVelocity().Length() * 0.01;

		if (flDamage >= m_iHealth)
		{
			m_takedamage = DAMAGE_YES;

			SetTouch( NULL );
			OnTakeDamage( CTakeDamageInfo( pOther, pOther, flDamage, DMG_CRUSH ) );

			// do a little damage to player if we broke glass or computer
			CTakeDamageInfo info( pOther, pOther, flDamage/4, DMG_SLASH );
			CalculateMeleeDamageForce( &info, (pOther->GetAbsOrigin() - GetAbsOrigin()), GetAbsOrigin() );
			pOther->TakeDamage( info );
		}
	}

	// can I be broken when stood upon?
	if ( HasSpawnFlags( SF_BREAK_PRESSURE ) && pOther->GetGroundEntity() == this )
	{
		// play creaking sound here.
		DamageSound();

		m_hBreaker = pOther;

		SetThink ( &CBreakable::Die );
		SetTouch( NULL );
		
		// Add optional delay 
		SetNextThink( gpGlobals->curtime + m_flPressureDelay );

	}
}


//-----------------------------------------------------------------------------
// Purpose: Input handler for adding to the breakable's health.
// Input  : Integer health points to add.
//-----------------------------------------------------------------------------
void CBreakable::InputAddHealth( inputdata_t &inputdata )
{
	UpdateHealth( m_iHealth + inputdata.value.Int(), inputdata.pActivator );
}


//-----------------------------------------------------------------------------
// Purpose: Input handler for breaking the breakable immediately.
//-----------------------------------------------------------------------------
void CBreakable::InputBreak( inputdata_t &inputdata )
{
	Break( inputdata.pActivator );
}


//-----------------------------------------------------------------------------
// Purpose: Input handler for removing health from the breakable.
// Input  : Integer health points to remove.
//-----------------------------------------------------------------------------
void CBreakable::InputRemoveHealth( inputdata_t &inputdata )
{
	UpdateHealth( m_iHealth - inputdata.value.Int(), inputdata.pActivator );
}


//-----------------------------------------------------------------------------
// Purpose: Input handler for setting the breakable's health.
//-----------------------------------------------------------------------------
void CBreakable::InputSetHealth( inputdata_t &inputdata )
{
	UpdateHealth( inputdata.value.Int(), inputdata.pActivator );
}


//-----------------------------------------------------------------------------
// Purpose: Choke point for changes to breakable health. Ensures outputs are fired.
// Input  : iNewHealth - 
//			pActivator - 
// Output : Returns true if the breakable survived, false if it died (broke).
//-----------------------------------------------------------------------------
bool CBreakable::UpdateHealth( int iNewHealth, CBaseEntity *pActivator )
{
	if ( iNewHealth != m_iHealth )
	{
		m_iHealth = iNewHealth;

		if ( m_iMaxHealth == 0 )
		{
			Assert( false );
			m_iMaxHealth = 1;
		}

		// Output the new health as a percentage of max health [0..1]
		float flRatio = clamp( (float)m_iHealth / (float)m_iMaxHealth, 0, 1 );
		m_OnHealthChanged.Set( flRatio, pActivator, this );

		if ( m_iHealth <= 0 )
		{
			Break( pActivator );
			return false;
		}
		else
		{
			if ( FBitSet( m_spawnflags, SF_BREAK_TRIGGER_ONLY ) )
			{
				m_takedamage = DAMAGE_NO;
			}
			else
			{
				m_takedamage = DAMAGE_YES;
			}
		}
	}

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: Breaks the breakable if it can be broken.
// Input  : pBreaker - The entity that caused us to break, either via an input,
//				by shooting us, or by touching us.
//-----------------------------------------------------------------------------
void CBreakable::Break( CBaseEntity *pBreaker )
{
	if ( IsBreakable() )
	{
		QAngle angles = GetLocalAngles();
		angles.y = m_angle;
		SetLocalAngles( angles );
		m_hBreaker = pBreaker;
		Die();
	}
}


void CBreakable::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr )
{
	// random spark if this is a 'computer' object
	if (random->RandomInt(0,1) )
	{
		switch( m_Material )
		{
			case matComputer:
			{
				g_pEffects->Sparks( ptr->endpos );

				EmitSound( "Breakable.Computer" );
			}
			break;
			
			case matUnbreakableGlass:
				g_pEffects->Ricochet( ptr->endpos, (vecDir*-1.0f) );
			break;
		}
	}

	BaseClass::TraceAttack( info, vecDir, ptr );
}


//-----------------------------------------------------------------------------
// Purpose: Allows us to take damage from physics objects
//-----------------------------------------------------------------------------
void CBreakable::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
{
	BaseClass::VPhysicsCollision( index, pEvent );

	Vector damagePos;
	pEvent->pInternalData->GetContactPoint( damagePos );
	Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass();
	if ( damageForce == vec3_origin )
	{
		// This can happen if this entity is a func_breakable, and can't move.
		// Use the velocity of the entity that hit us instead.
		damageForce = pEvent->postVelocity[!index] * pEvent->pObjects[!index]->GetMass();
	}

	// If we're supposed to explode on collision, do so
	if ( HasSpawnFlags( SF_BREAK_PHYSICS_BREAK_IMMEDIATELY ) )
	{
		// We're toast
		m_bTookPhysicsDamage = true;
		CBaseEntity *pHitEntity = pEvent->pEntities[!index];

		CTakeDamageInfo dmgInfo( pHitEntity, pHitEntity, damageForce, damagePos, (m_iHealth + 1), DMG_CRUSH );
		PhysCallbackDamage( this, dmgInfo, *pEvent, index );
	}
	else if ( !HasSpawnFlags( SF_BREAK_DONT_TAKE_PHYSICS_DAMAGE ) )
	{
		int otherIndex = !index;
		CBaseEntity *pOther = pEvent->pEntities[otherIndex];

		// We're to take normal damage from this
		int damageType;
		IBreakableWithPropData *pBreakableInterface = assert_cast<IBreakableWithPropData*>(this);
		float damage = CalculateDefaultPhysicsDamage( index, pEvent, m_impactEnergyScale, true, damageType, pBreakableInterface->GetPhysicsDamageTable() );
		if ( damage > 0 )
		{
			CTakeDamageInfo dmgInfo( pOther, pOther, damageForce, damagePos, damage, damageType );
			PhysCallbackDamage( this, dmgInfo, *pEvent, index );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Allows us to make damage exceptions that are breakable-specific.
//-----------------------------------------------------------------------------
int CBreakable::OnTakeDamage( const CTakeDamageInfo &info )
{
	Vector	vecTemp;

	CTakeDamageInfo subInfo = info;

	// If attacker can't do at least the min required damage to us, don't take any damage from them
	if ( m_takedamage == DAMAGE_NO || info.GetDamage() < m_iMinHealthDmg )
		return 0;

	// Check our damage filter
	if ( !PassesDamageFilter(subInfo) )
	{
		m_bTookPhysicsDamage = false;
		return 1;
	}

	vecTemp = subInfo.GetInflictor()->GetAbsOrigin() - WorldSpaceCenter();

	if (!IsBreakable())
		return 0;

	float flPropDamage = GetBreakableDamage( subInfo, assert_cast<IBreakableWithPropData*>(this) );
	subInfo.SetDamage( flPropDamage );
	
	int iPrevHealth = m_iHealth;
	BaseClass::OnTakeDamage( subInfo );

	// HACK: slam health back to what it was so UpdateHealth can do its thing
	int iNewHealth = m_iHealth;
	m_iHealth = iPrevHealth;
	if ( !UpdateHealth( iNewHealth, info.GetAttacker() ) )
		return 1;

	// Make a shard noise each time func breakable is hit, if it's capable of taking damage
	if ( m_takedamage == DAMAGE_YES )
	{
		// Don't play shard noise if being burned.
		// Don't play shard noise if cbreakable actually died.
		if ( ( subInfo.GetDamageType() & DMG_BURN ) == false )
		{
			DamageSound();
		}
	}

	return 1;
}

//------------------------------------------------------------------------------
// Purpose : Reset the OnGround flags for any entities that may have been
//			 resting on me
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CBreakable::ResetOnGroundFlags(void)
{
	// !!! HACK  This should work!
	// Build a box above the entity that looks like an 9 inch high sheet
	Vector mins, maxs;
	CollisionProp()->WorldSpaceAABB( &mins, &maxs );
	mins.z -= 1;
	maxs.z += 8;

	// BUGBUG -- can only find 256 entities on a breakable -- should be enough
	CBaseEntity *pList[256];
	int count = UTIL_EntitiesInBox( pList, 256, mins, maxs, FL_ONGROUND );
	if ( count )
	{
		for ( int i = 0; i < count; i++ )
		{
			pList[i]->SetGroundEntity( (CBaseEntity *)NULL );
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Breaks the breakable. m_hBreaker is the entity that caused us to break.
//-----------------------------------------------------------------------------
void CBreakable::Die( void )
{
	Vector vecVelocity;// shard velocity
	char cFlag = 0;
	int pitch;
	float fvol;
	
	pitch = 95 + random->RandomInt(0,29);

	if (pitch > 97 && pitch < 103)
	{
		pitch = 100;
	}

	// The more negative m_iHealth, the louder
	// the sound should be.

	fvol = random->RandomFloat(0.85, 1.0) + (abs(m_iHealth) / 100.0);
	if (fvol > 1.0)
	{
		fvol = 1.0;
	}

	const char *soundname = NULL;

	switch (m_Material)
	{
	default:
		break;

	case matGlass:
		soundname = "Breakable.Glass";
		cFlag = BREAK_GLASS;
#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out
		TheBots->OnEvent( EVENT_BREAK_GLASS, this );  
#endif
		break;

	case matWood:
		soundname = "Breakable.Crate";
		cFlag = BREAK_WOOD;
#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out
		TheBots->OnEvent( EVENT_BREAK_WOOD, this );  
#endif
		break;

	case matComputer:
		soundname = "Breakable.Computer";
		cFlag = BREAK_METAL;
#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out
		TheBots->OnEvent( EVENT_BREAK_METAL, this );  
#endif
		break;

	case matMetal:
		soundname = "Breakable.Metal";
		cFlag = BREAK_METAL;
#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out
		TheBots->OnEvent( EVENT_BREAK_METAL, this );  
#endif
		break;

	case matFlesh:
	case matWeb:
		soundname = "Breakable.Flesh";
		cFlag = BREAK_FLESH;
#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out
		TheBots->OnEvent( EVENT_BREAK_FLESH, this );  
#endif
		break;

	case matRocks:
	case matCinderBlock:
		soundname = "Breakable.Concrete";
		cFlag = BREAK_CONCRETE;
#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out
		TheBots->OnEvent( EVENT_BREAK_CONCRETE, this );  
#endif
		break;

	case matCeilingTile:
		soundname = "Breakable.Ceiling";
		break;
	}
    
	if ( soundname )
	{
		CSoundParameters params;
		if ( GetParametersForSound( soundname, params, NULL ) )
		{
			CPASAttenuationFilter filter( this );

			EmitSound_t ep;
			ep.m_nChannel = params.channel;
			ep.m_pSoundName = params.soundname;
			ep.m_flVolume = fvol;
			ep.m_SoundLevel = params.soundlevel;
			ep.m_nPitch = pitch;

			EmitSound( filter, entindex(), ep );	
		}
	}
		
	switch( m_Explosion )
	{
	case expDirected:
		vecVelocity = g_vecAttackDir * -200;
		break;

	case expUsePrecise:
		{
			AngleVectors( m_GibDir, &vecVelocity, NULL, NULL );
			vecVelocity *= 200;
		}
		break;

	case expRandom:
		vecVelocity.x = 0;
		vecVelocity.y = 0;
		vecVelocity.z = 0;
		break;

	default:
		DevMsg("**ERROR - Unspecified gib dir method in func_breakable!\n");
		break;
	}

	Vector vecSpot = WorldSpaceCenter();
	CPVSFilter filter2( vecSpot );

	int iModelIndex = 0;
	CCollisionProperty *pCollisionProp = CollisionProp();

	Vector vSize = pCollisionProp->OBBSize();
	int iCount = ( vSize[0] * vSize[1] + vSize[1] * vSize[2] + vSize[2] * vSize[0] ) / ( 3 * 12 * 12 );

	if ( iCount > func_break_max_pieces.GetInt() )
	{
		iCount = func_break_max_pieces.GetInt();
	}

	for ( int i = 0; i < iCount; i++ )
	{

#ifdef HL1_DLL
		// Use the passed model instead of the propdata type
		const char *modelName = STRING( m_iszModelName );
		
		// if the map specifies a model by name
		if( strstr( modelName, ".mdl" ) != NULL )
		{
			iModelIndex = modelinfo->GetModelIndex( modelName );
		}
		else	// do the hl2 / normal way
#endif

		iModelIndex = modelinfo->GetModelIndex( g_PropDataSystem.GetRandomChunkModel(  STRING( m_iszModelName ) ) );
		

		te->BreakModel( filter2, 0.0, 
			vecSpot, pCollisionProp->GetCollisionAngles(), vSize, 
			vecVelocity, iModelIndex, 100, 1, 2.5, cFlag );
	}

	ResetOnGroundFlags();

	// Don't fire something that could fire myself
	SetName( NULL_STRING );

	AddSolidFlags( FSOLID_NOT_SOLID );
	
	// Fire targets on break
	m_OnBreak.FireOutput( m_hBreaker, this );

	VPhysicsDestroyObject();
	SetThink( &CBreakable::SUB_Remove );
	SetNextThink( gpGlobals->curtime + 0.1f );
	if ( m_iszSpawnObject != NULL_STRING )
	{
		CBaseEntity::Create( STRING(m_iszSpawnObject), vecSpot, pCollisionProp->GetCollisionAngles(), this );
	}

	if ( Explodable() )
	{
		ExplosionCreate( vecSpot, pCollisionProp->GetCollisionAngles(), this, GetExplosiveDamage(), GetExplosiveRadius(), true );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Returns whether this object can be broken.
//-----------------------------------------------------------------------------
bool CBreakable::IsBreakable( void ) 
{ 
	return m_Material != matUnbreakableGlass;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
char const *CBreakable::DamageDecal( int bitsDamageType, int gameMaterial )
{
	if ( m_Material == matGlass  )
		return "GlassBreak";

	if ( m_Material == matUnbreakableGlass )
		return "BulletProof";

	return BaseClass::DamageDecal( bitsDamageType, gameMaterial );
}

//-----------------------------------------------------------------------------
// Purpose: Draw any debug text overlays
// Output : Current text offset from the top
//-----------------------------------------------------------------------------
int CBreakable::DrawDebugTextOverlays(void) 
{
	int text_offset = BaseClass::DrawDebugTextOverlays();

	if (m_debugOverlays & OVERLAY_TEXT_BIT) 
	{
		if ( GetMaxHealth() )
		{
			char tempstr[512];
			Q_snprintf(tempstr,sizeof(tempstr),"Health: %i",GetHealth());
			NDebugOverlay::EntityText(entindex(),text_offset,tempstr,0);
			text_offset++;
		}

		if ( m_iszBasePropData != NULL_STRING )
		{
			char tempstr[512];
			Q_snprintf(tempstr, sizeof(tempstr),"Base PropData: %s", STRING(m_iszBasePropData) );
			NDebugOverlay::EntityText(entindex(), text_offset, tempstr, 0);
			text_offset++;
		}
	}

	return text_offset;
}


//-----------------------------------------------------------------------------
// Purpose: Keep track of physgun influence
//-----------------------------------------------------------------------------

void CBreakable::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
{
	m_hPhysicsAttacker = pPhysGunUser;
	m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
}

void CBreakable::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t reason )
{
	m_hPhysicsAttacker = pPhysGunUser;
	m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
}

CBasePlayer *CBreakable::HasPhysicsAttacker( float dt )
{
	if (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime)
	{
		return m_hPhysicsAttacker;
	}
	return NULL;
}


//=============================================================================================================================
// PUSHABLE
//=============================================================================================================================

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
class CPushable : public CBreakable
{
public:
	DECLARE_CLASS( CPushable, CBreakable );

	void	Spawn ( void );
	bool	CreateVPhysics( void );
	void	Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );

	virtual int	ObjectCaps( void ) { return BaseClass::ObjectCaps() | FCAP_ONOFF_USE; }

	// breakables use an overridden takedamage
	virtual int OnTakeDamage( const CTakeDamageInfo &info );
	virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
	unsigned int PhysicsSolidMaskForEntity( void ) const { return MASK_PLAYERSOLID; }
};


LINK_ENTITY_TO_CLASS( func_pushable, CPushable );


void CPushable::Spawn( void )
{
	if ( HasSpawnFlags( SF_PUSH_BREAKABLE ) )
	{
		BaseClass::Spawn();
	}
	else
	{
		Precache();

		SetSolid( SOLID_VPHYSICS );

		SetMoveType( MOVETYPE_PUSH );
		SetModel( STRING( GetModelName() ) );

		CreateVPhysics();
	}

#ifdef HL1_DLL
	// Force HL1 Pushables to stay axially aligned.
	VPhysicsGetObject()->SetInertia( Vector( 1e30, 1e30, 1e30 ) );
#endif//HL1_DLL
}


bool CPushable::CreateVPhysics( void )
{
	VPhysicsInitNormal( SOLID_VPHYSICS, 0, false );
	IPhysicsObject *pPhysObj = VPhysicsGetObject();
	if ( pPhysObj )
	{
		pPhysObj->SetMass( 30 );
//		Vector vecInertia = Vector(800, 800, 800);
//		pPhysObj->SetInertia( vecInertia );
	}

	return true;
}

// Pull the func_pushable
void CPushable::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
#ifdef HL1_DLL
	if( m_spawnflags & SF_PUSH_NO_USE )
		return;

	// Allow pushables to be dragged by player
	CBasePlayer *pPlayer = ToBasePlayer( pActivator );
	if ( pPlayer )
	{
		if ( useType == USE_ON )
		{
			PlayerPickupObject( pPlayer, this );
		}
	}
#else
	BaseClass::Use( pActivator, pCaller, useType, value );
#endif
}


int CPushable::OnTakeDamage( const CTakeDamageInfo &info )
{
	if ( m_spawnflags & SF_PUSH_BREAKABLE )
		return BaseClass::OnTakeDamage( info );

	return 1;
}

//-----------------------------------------------------------------------------
// Purpose: Allows us to take damage from physics objects
//-----------------------------------------------------------------------------
void CPushable::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
{
	int otherIndex = !index;
	CBaseEntity *pOther = pEvent->pEntities[otherIndex];
	if ( pOther->IsPlayer() )
	{
		// Pushables don't take damage from impacts with the player
		// We call all the way back to the baseclass to get the physics effects.
		CBaseEntity::VPhysicsCollision( index, pEvent );
		return;
	}

	BaseClass::VPhysicsCollision( index, pEvent );
}

