From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/server/func_break.cpp | 2642 ++++++++++++++++++------------------- 1 file changed, 1321 insertions(+), 1321 deletions(-) (limited to 'mp/src/game/server/func_break.cpp') diff --git a/mp/src/game/server/func_break.cpp b/mp/src/game/server/func_break.cpp index ecb70082..d0b815e2 100644 --- a/mp/src/game/server/func_break.cpp +++ b/mp/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(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 ); - } - } - -#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(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 ); + } + } + +#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 ); +} + -- cgit v1.2.3