aboutsummaryrefslogtreecommitdiff
path: root/sp/src/game/server/func_break.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /sp/src/game/server/func_break.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'sp/src/game/server/func_break.cpp')
-rw-r--r--sp/src/game/server/func_break.cpp1321
1 files changed, 1321 insertions, 0 deletions
diff --git a/sp/src/game/server/func_break.cpp b/sp/src/game/server/func_break.cpp
new file mode 100644
index 00000000..ecb70082
--- /dev/null
+++ b/sp/src/game/server/func_break.cpp
@@ -0,0 +1,1321 @@
+//========= Copyright 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 "tier0/icommandline.h"
+
+#ifdef PORTAL
+ #include "portal_shareddefs.h"
+ #include "portal_util_shared.h"
+ #include "prop_portal_shared.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 );
+ConVar func_break_reduction_factor( "func_break_reduction_factor", ".5" );
+
+#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 - split into two strings to avoid trigraph warning
+ "weapon_stunstick", // 16
+ "unused (weapon_ar1) 17", // 17
+ "weapon_ar2", // 18
+ "unused (???"") 19", // 19 - split into two strings to avoid trigraph warning
+ "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_KEYFIELD( m_PerformanceMode, FIELD_INTEGER, "PerformanceMode" ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "Break", InputBreak ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "AddHealth", InputAddHealth ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveHealth", InputRemoveHealth ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "SetMass", InputSetMass ),
+
+ // 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
+
+#if HL2_EPISODIC
+ case matNone:
+ pGibName = "";
+ 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;
+ const 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: Input handler for setting the breakable's mass.
+//-----------------------------------------------------------------------------
+void CBreakable::InputSetMass( inputdata_t &inputdata )
+{
+ IPhysicsObject * vPhys = VPhysicsGetObject();
+ if ( vPhys )
+ {
+ float toMass = inputdata.value.Float();
+ Assert(toMass > 0);
+ vPhys->SetMass( toMass );
+ }
+ else
+ {
+ Warning( "Tried to call SetMass() on %s but it has no physics.\n", GetEntityName().ToCStr() );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// 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.f, 1.f );
+ 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, CDmgAccumulator *pAccumulator )
+{
+ // 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, pAccumulator );
+}
+
+
+//-----------------------------------------------------------------------------
+// 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];
+
+ // HACKHACK: Reset mass to get correct collision response for the object breaking this glass
+ if ( m_Material == matGlass )
+ {
+ pEvent->pObjects[index]->SetMass( 2.0f );
+ }
+ 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 )
+ {
+ // HACKHACK: Reset mass to get correct collision response for the object breaking this glass
+ if ( m_Material == matGlass )
+ {
+ pEvent->pObjects[index]->SetMass( 2.0f );
+ }
+ 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 );
+ }
+ }
+
+#ifdef PORTAL
+ // !!! HACK This should work!
+ // Tell touching portals to fizzle
+ int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
+ if( iPortalCount != 0 )
+ {
+ Vector vMin, vMax;
+ CollisionProp()->WorldSpaceAABB( &vMin, &vMax );
+
+ Vector vBoxCenter = ( vMin + vMax ) * 0.5f;
+ Vector vBoxExtents = ( vMax - vMin ) * 0.5f;
+
+ CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
+ for( int i = 0; i != iPortalCount; ++i )
+ {
+ CProp_Portal *pTempPortal = pPortals[i];
+ if( UTIL_IsBoxIntersectingPortal( vBoxCenter, vBoxExtents, pTempPortal ) )
+ {
+ pTempPortal->DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
+ pTempPortal->Fizzle();
+ }
+ }
+ }
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// 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;
+ break;
+
+ case matWood:
+ soundname = "Breakable.Crate";
+ cFlag = BREAK_WOOD;
+ break;
+
+ case matComputer:
+ soundname = "Breakable.Computer";
+ cFlag = BREAK_METAL;
+ break;
+
+ case matMetal:
+ soundname = "Breakable.Metal";
+ cFlag = BREAK_METAL;
+ break;
+
+ case matFlesh:
+ case matWeb:
+ soundname = "Breakable.Flesh";
+ cFlag = BREAK_FLESH;
+ break;
+
+ case matRocks:
+ case matCinderBlock:
+ soundname = "Breakable.Concrete";
+ cFlag = BREAK_CONCRETE;
+ break;
+
+ case matCeilingTile:
+ soundname = "Breakable.Ceiling";
+ break;
+ }
+
+ if ( soundname )
+ {
+ if ( m_hBreaker && m_hBreaker->IsPlayer() )
+ {
+ IGameEvent * event = gameeventmanager->CreateEvent( "break_breakable" );
+ if ( event )
+ {
+ event->SetInt( "userid", ToBasePlayer( m_hBreaker )->GetUserID() );
+ event->SetInt( "entindex", entindex() );
+ event->SetInt( "material", cFlag );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+
+ 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();
+ }
+
+ ConVarRef breakable_disable_gib_limit( "breakable_disable_gib_limit" );
+ if ( !breakable_disable_gib_limit.GetBool() && iCount )
+ {
+ if ( m_PerformanceMode == PM_NO_GIBS )
+ {
+ iCount = 0;
+ }
+ else if ( m_PerformanceMode == PM_REDUCED_GIBS )
+ {
+ int iNewCount = iCount * func_break_reduction_factor.GetFloat();
+ iCount = MAX( iNewCount, 1 );
+ }
+ }
+
+ if ( m_iszModelName != NULL_STRING )
+ {
+ 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 ) ) );
+
+ // All objects except the first one in this run are marked as slaves...
+ int slaveFlag = 0;
+ if ( i != 0 )
+ {
+ slaveFlag = BREAK_SLAVE;
+ }
+
+ te->BreakModel( filter2, 0.0,
+ vecSpot, pCollisionProp->GetCollisionAngles(), vSize,
+ vecVelocity, iModelIndex, 100, 1, 2.5, cFlag | slaveFlag );
+ }
+ }
+
+ 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());
+ EntityText(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) );
+ EntityText( 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 );
+}
+