|
|
//========= 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"
#ifdef PORTAL
#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" ); 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 - 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
};
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 ( StringHasPrefixCaseSensitive( STRING(m_iszPropData), "None" ) ) return;
g_PropDataSystem.ParsePropFromBase( this, 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: return "Breakable.MatFlesh"; case matComputer: return "Breakable.Computer"; case matUnbreakableGlass: case matGlass: return "Breakable.MatGlass"; case matMetalPanel: 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 matMetalPanel: pGibName = "MetalPanelChunks"; 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 matMetalPanel: 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<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, 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 and it took damage
if ( m_takedamage == DAMAGE_YES && m_iHealth < iPrevHealth ) { // 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.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: case matMetalPanel: soundname = "Breakable.Metal"; cFlag = BREAK_METAL; break;
case matFlesh: 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 ); }
|