//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: Implements breakables and pushables. func_breakable is a bmodel // that breaks into pieces after taking damage. // //=============================================================================// #include "cbase.h" #include "player.h" #include "filters.h" #include "func_break.h" #include "decals.h" #include "explode.h" #include "in_buttons.h" #include "physics.h" #include "IEffects.h" #include "vstdlib/random.h" #include "engine/IEngineSound.h" #include "SoundEmitterSystem/isoundemittersystembase.h" #include "globals.h" #include "util.h" #include "physics_impact_damage.h" #include "tier0/icommandline.h" // 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" ); extern ConVar breakable_disable_gib_limit; 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 const char *CBreakable::pSpawnObjects[] = { NULL, // 0 "item_battery", // 1 "item_healthkit", // 2 "item_ammo_pistol", // 3 "item_ammo_pistol_large", // 4 "item_ammo_smg1", // 5 "item_ammo_smg1_large", // 6 "item_ammo_ar2", // 7 "item_ammo_ar2_large", // 8 "item_box_buckshot", // 9 "item_flare_round", // 10 "item_box_flare_rounds", // 11 "item_rpg_round", // 12 "unused (item_smg1_grenade) 13",// 13 "item_box_sniper_rounds", // 14 "unused (???) 15", // 15 "weapon_stunstick", // 16 "unused (weapon_ar1) 17", // 17 "weapon_ar2", // 18 "unused (???) 19", // 19 "weapon_rpg", // 20 "weapon_smg1", // 21 "unused (weapon_smg2) 22", // 22 "unused (weapon_slam) 23", // 23 "weapon_shotgun", // 24 "unused (weapon_molotov) 25",// 25 "item_dynamic_resupply", // 26 }; 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_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_flDmgModFire, 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(); m_flDmgModFire = 1.0f; 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 ); 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; case matCinderBlock: pGibName = "ConcreteChunks"; break; #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); } m_iszModelName = MAKE_STRING( pGibName ); // Precache the spawn item's data if ( !CommandLine()->CheckParm("-makereslists")) { if ( m_iszSpawnObject != NULL_STRING ) { UTIL_PrecacheOther( STRING( m_iszSpawnObject ) ); } } else { // Actually, precache all possible objects... for ( int i = 0; i < ARRAYSIZE(pSpawnObjects) ; ++i ) { if ( !pSpawnObjects[ i ] ) continue; if ( !Q_strnicmp( pSpawnObjects[ i ], "unused", Q_strlen( "unused" ) ) ) continue; UTIL_PrecacheOther( pSpawnObjects[ i ] ); } } PrecacheScriptSound( "Breakable.MatGlass" ); PrecacheScriptSound( "Breakable.MatWood" ); PrecacheScriptSound( "Breakable.MatMetal" ); PrecacheScriptSound( "Breakable.MatFlesh" ); PrecacheScriptSound( "Breakable.MatConcrete" ); PrecacheScriptSound( "Breakable.Computer" ); PrecacheScriptSound( "Breakable.Crate" ); PrecacheScriptSound( "Breakable.Glass" ); PrecacheScriptSound( "Breakable.Metal" ); PrecacheScriptSound( "Breakable.Flesh" ); PrecacheScriptSound( "Breakable.Concrete" ); PrecacheScriptSound( "Breakable.Ceiling" ); } // play shard sound when func_breakable takes damage. // the more damage, the louder the shard sound. void CBreakable::DamageSound( void ) { int pitch; float fvol; char *soundname = NULL; int material = m_Material; if (random->RandomInt(0,2)) { pitch = PITCH_NORM; } else { pitch = 95 + random->RandomInt(0,34); } fvol = random->RandomFloat(0.75, 1.0); if (material == matComputer && random->RandomInt(0,1)) { material = matMetal; } switch (material) { case matGlass: case matUnbreakableGlass: soundname = "Breakable.MatGlass"; break; case matWood: soundname = "Breakable.MatWood"; break; case matMetal: soundname = "Breakable.MatMetal"; break; case matRocks: case matCinderBlock: soundname = "Breakable.MatConcrete"; break; case matComputer: soundname = "Breakable.Computer"; break; default: break; } if ( soundname ) { CSoundParameters params; if ( GetParametersForSound( soundname, params, NULL ) ) { CPASAttenuationFilter filter( this ); EmitSound_t ep; ep.m_nChannel = params.channel; ep.m_pSoundName = params.soundname; ep.m_flVolume = fvol; ep.m_SoundLevel = params.soundlevel; ep.m_nPitch = pitch; EmitSound( filter, entindex(), ep ); } } } void CBreakable::BreakTouch( CBaseEntity *pOther ) { float flDamage; // only players can break these right now if ( !pOther->IsPlayer() || !IsBreakable() ) { return; } // can I be broken when run into? if ( HasSpawnFlags( SF_BREAK_TOUCH ) ) { flDamage = pOther->GetSmoothedVelocity().Length() * 0.01; if (flDamage >= m_iHealth) { m_takedamage = DAMAGE_YES; SetTouch( NULL ); OnTakeDamage( CTakeDamageInfo( pOther, pOther, flDamage, DMG_CRUSH ) ); // do a little damage to player if we broke glass or computer CTakeDamageInfo info( pOther, pOther, flDamage/4, DMG_SLASH ); CalculateMeleeDamageForce( &info, (pOther->GetAbsOrigin() - GetAbsOrigin()), GetAbsOrigin() ); pOther->TakeDamage( info ); } } // can I be broken when stood upon? if ( HasSpawnFlags( SF_BREAK_PRESSURE ) && pOther->GetGroundEntity() == this ) { // play creaking sound here. DamageSound(); m_hBreaker = pOther; SetThink ( &CBreakable::Die ); SetTouch( NULL ); // Add optional delay SetNextThink( gpGlobals->curtime + m_flPressureDelay ); } } //----------------------------------------------------------------------------- // Purpose: Input handler for adding to the breakable's health. // Input : Integer health points to add. //----------------------------------------------------------------------------- void CBreakable::InputAddHealth( inputdata_t &inputdata ) { UpdateHealth( m_iHealth + inputdata.value.Int(), inputdata.pActivator ); } //----------------------------------------------------------------------------- // Purpose: Input handler for breaking the breakable immediately. //----------------------------------------------------------------------------- void CBreakable::InputBreak( inputdata_t &inputdata ) { Break( inputdata.pActivator ); } //----------------------------------------------------------------------------- // Purpose: Input handler for removing health from the breakable. // Input : Integer health points to remove. //----------------------------------------------------------------------------- void CBreakable::InputRemoveHealth( inputdata_t &inputdata ) { UpdateHealth( m_iHealth - inputdata.value.Int(), inputdata.pActivator ); } //----------------------------------------------------------------------------- // Purpose: Input handler for setting the breakable's health. //----------------------------------------------------------------------------- void CBreakable::InputSetHealth( inputdata_t &inputdata ) { UpdateHealth( inputdata.value.Int(), inputdata.pActivator ); } //----------------------------------------------------------------------------- // Purpose: 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, 1 ); m_OnHealthChanged.Set( flRatio, pActivator, this ); if ( m_iHealth <= 0 ) { Break( pActivator ); return false; } else { if ( FBitSet( m_spawnflags, SF_BREAK_TRIGGER_ONLY ) ) { m_takedamage = DAMAGE_NO; } else { m_takedamage = DAMAGE_YES; } } } return true; } //----------------------------------------------------------------------------- // Purpose: Breaks the breakable if it can be broken. // Input : pBreaker - The entity that caused us to break, either via an input, // by shooting us, or by touching us. //----------------------------------------------------------------------------- void CBreakable::Break( CBaseEntity *pBreaker ) { if ( IsBreakable() ) { m_hBreaker = pBreaker; Die(); } } void CBreakable::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ) { // random spark if this is a 'computer' object if (random->RandomInt(0,1) ) { switch( m_Material ) { case matComputer: { g_pEffects->Sparks( ptr->endpos ); EmitSound( "Breakable.Computer" ); } break; case matUnbreakableGlass: g_pEffects->Ricochet( ptr->endpos, (vecDir*-1.0f) ); break; } } BaseClass::TraceAttack( info, vecDir, ptr ); } //----------------------------------------------------------------------------- // Purpose: Allows us to take damage from physics objects //----------------------------------------------------------------------------- void CBreakable::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) { BaseClass::VPhysicsCollision( index, pEvent ); Vector damagePos; pEvent->pInternalData->GetContactPoint( damagePos ); Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass(); if ( damageForce == vec3_origin ) { // This can happen if this entity is a func_breakable, and can't move. // Use the velocity of the entity that hit us instead. damageForce = pEvent->postVelocity[!index] * pEvent->pObjects[!index]->GetMass(); } // If we're supposed to explode on collision, do so if ( HasSpawnFlags( SF_BREAK_PHYSICS_BREAK_IMMEDIATELY ) ) { // We're toast m_bTookPhysicsDamage = true; CBaseEntity *pHitEntity = pEvent->pEntities[!index]; // 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(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(this) ); subInfo.SetDamage( flPropDamage ); int iPrevHealth = m_iHealth; BaseClass::OnTakeDamage( subInfo ); // HACK: slam health back to what it was so UpdateHealth can do its thing int iNewHealth = m_iHealth; m_iHealth = iPrevHealth; if ( !UpdateHealth( iNewHealth, info.GetAttacker() ) ) return 1; // Make a shard noise each time func breakable is hit, if it's capable of taking damage if ( m_takedamage == DAMAGE_YES ) { // Don't play shard noise if being burned. // Don't play shard noise if cbreakable actually died. if ( ( subInfo.GetDamageType() & DMG_BURN ) == false ) { DamageSound(); } } return 1; } //------------------------------------------------------------------------------ // Purpose : Reset the OnGround flags for any entities that may have been // resting on me // Input : // Output : //------------------------------------------------------------------------------ void CBreakable::ResetOnGroundFlags(void) { // !!! HACK This should work! // Build a box above the entity that looks like an 9 inch high sheet Vector mins, maxs; CollisionProp()->WorldSpaceAABB( &mins, &maxs ); mins.z -= 1; maxs.z += 8; // BUGBUG -- can only find 256 entities on a breakable -- should be enough CBaseEntity *pList[256]; int count = UTIL_EntitiesInBox( pList, 256, mins, maxs, FL_ONGROUND ); if ( count ) { for ( int i = 0; i < count; i++ ) { pList[i]->SetGroundEntity( (CBaseEntity *)NULL ); } } } //----------------------------------------------------------------------------- // Purpose: Breaks the breakable. m_hBreaker is the entity that caused us to break. //----------------------------------------------------------------------------- void CBreakable::Die( void ) { Vector vecVelocity;// shard velocity char cFlag = 0; int pitch; float fvol; pitch = 95 + random->RandomInt(0,29); if (pitch > 97 && pitch < 103) { pitch = 100; } // The more negative m_iHealth, the louder // the sound should be. fvol = random->RandomFloat(0.85, 1.0) + (abs(m_iHealth.Get()) / 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(); } 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++ ) { 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(); } } 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 ) { BaseClass::Use( pActivator, pCaller, useType, value ); } 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 ); }