diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /sp/src/game/server/func_break.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'sp/src/game/server/func_break.cpp')
| -rw-r--r-- | sp/src/game/server/func_break.cpp | 2642 |
1 files changed, 1321 insertions, 1321 deletions
diff --git a/sp/src/game/server/func_break.cpp b/sp/src/game/server/func_break.cpp index ecb70082..d0b815e2 100644 --- a/sp/src/game/server/func_break.cpp +++ b/sp/src/game/server/func_break.cpp @@ -1,1321 +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 );
-}
-
+//========= 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 ); +} + |