Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1225 lines
32 KiB

  1. //========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Implements breakables and pushables. func_breakable is a bmodel
  4. // that breaks into pieces after taking damage.
  5. //
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "player.h"
  9. #include "filters.h"
  10. #include "func_break.h"
  11. #include "decals.h"
  12. #include "explode.h"
  13. #include "in_buttons.h"
  14. #include "physics.h"
  15. #include "IEffects.h"
  16. #include "vstdlib/random.h"
  17. #include "engine/IEngineSound.h"
  18. #include "SoundEmitterSystem/isoundemittersystembase.h"
  19. #include "globals.h"
  20. #include "util.h"
  21. #include "physics_impact_damage.h"
  22. #include "tier0/icommandline.h"
  23. #ifdef PORTAL
  24. #include "prop_portal_shared.h"
  25. #endif
  26. // memdbgon must be the last include file in a .cpp file!!!
  27. #include "tier0/memdbgon.h"
  28. ConVar func_break_max_pieces( "func_break_max_pieces", "15", FCVAR_ARCHIVE | FCVAR_REPLICATED );
  29. ConVar func_break_reduction_factor( "func_break_reduction_factor", ".5" );
  30. extern ConVar breakable_disable_gib_limit;
  31. extern Vector g_vecAttackDir;
  32. // Just add more items to the bottom of this array and they will automagically be supported
  33. // This is done instead of just a classname in the FGD so we can control which entities can
  34. // be spawned, and still remain fairly flexible
  35. const char *CBreakable::pSpawnObjects[] =
  36. {
  37. NULL, // 0
  38. "item_battery", // 1
  39. "item_healthkit", // 2
  40. "item_ammo_pistol", // 3
  41. "item_ammo_pistol_large", // 4
  42. "item_ammo_smg1", // 5
  43. "item_ammo_smg1_large", // 6
  44. "item_ammo_ar2", // 7
  45. "item_ammo_ar2_large", // 8
  46. "item_box_buckshot", // 9
  47. "item_flare_round", // 10
  48. "item_box_flare_rounds", // 11
  49. "item_rpg_round", // 12
  50. "unused (item_smg1_grenade) 13",// 13
  51. "item_box_sniper_rounds", // 14
  52. "unused (???"") 15", // 15 - split into two strings to avoid trigraph warning
  53. "weapon_stunstick", // 16
  54. "unused (weapon_ar1) 17", // 17
  55. "weapon_ar2", // 18
  56. "unused (???"") 19", // 19 - split into two strings to avoid trigraph warning
  57. "weapon_rpg", // 20
  58. "weapon_smg1", // 21
  59. "unused (weapon_smg2) 22", // 22
  60. "unused (weapon_slam) 23", // 23
  61. "weapon_shotgun", // 24
  62. "unused (weapon_molotov) 25",// 25
  63. "item_dynamic_resupply", // 26
  64. };
  65. const char *pFGDPropData[] =
  66. {
  67. NULL,
  68. "Wooden.Tiny",
  69. "Wooden.Small",
  70. "Wooden.Medium",
  71. "Wooden.Large",
  72. "Wooden.Huge",
  73. "Metal.Small",
  74. "Metal.Medium",
  75. "Metal.Large",
  76. "Cardboard.Small",
  77. "Cardboard.Medium",
  78. "Cardboard.Large",
  79. "Stone.Small",
  80. "Stone.Medium",
  81. "Stone.Large",
  82. "Stone.Huge",
  83. "Glass.Small",
  84. "Plastic.Small",
  85. "Plastic.Medium",
  86. "Plastic.Large",
  87. "Pottery.Small",
  88. "Pottery.Medium",
  89. "Pottery.Large",
  90. "Pottery.Huge",
  91. "Glass.Window",
  92. };
  93. LINK_ENTITY_TO_CLASS( func_breakable, CBreakable );
  94. BEGIN_DATADESC( CBreakable )
  95. DEFINE_FIELD( m_Material, FIELD_INTEGER ),
  96. DEFINE_KEYFIELD( m_Explosion, FIELD_INTEGER, "explosion" ),
  97. DEFINE_KEYFIELD( m_GibDir, FIELD_VECTOR, "gibdir" ),
  98. DEFINE_FIELD( m_hBreaker, FIELD_EHANDLE ),
  99. // Don't need to save/restore these because we precache after restore
  100. //DEFINE_FIELD( m_idShard, FIELD_INTEGER ),
  101. DEFINE_FIELD( m_iszGibModel, FIELD_STRING ),
  102. DEFINE_FIELD( m_iszSpawnObject, FIELD_STRING ),
  103. DEFINE_KEYFIELD( m_ExplosionMagnitude, FIELD_INTEGER, "explodemagnitude" ),
  104. DEFINE_KEYFIELD( m_flPressureDelay, FIELD_FLOAT, "PressureDelay" ),
  105. DEFINE_KEYFIELD( m_iMinHealthDmg, FIELD_INTEGER, "minhealthdmg" ),
  106. DEFINE_FIELD( m_bTookPhysicsDamage, FIELD_BOOLEAN ),
  107. DEFINE_FIELD( m_iszPropData, FIELD_STRING ),
  108. DEFINE_INPUT( m_impactEnergyScale, FIELD_FLOAT, "physdamagescale" ),
  109. DEFINE_KEYFIELD( m_PerformanceMode, FIELD_INTEGER, "PerformanceMode" ),
  110. DEFINE_INPUTFUNC( FIELD_VOID, "Break", InputBreak ),
  111. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ),
  112. DEFINE_INPUTFUNC( FIELD_INTEGER, "AddHealth", InputAddHealth ),
  113. DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveHealth", InputRemoveHealth ),
  114. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetMass", InputSetMass ),
  115. // Function Pointers
  116. DEFINE_ENTITYFUNC( BreakTouch ),
  117. DEFINE_THINKFUNC( Die ),
  118. // Outputs
  119. DEFINE_OUTPUT(m_OnBreak, "OnBreak"),
  120. DEFINE_OUTPUT(m_OnHealthChanged, "OnHealthChanged"),
  121. DEFINE_FIELD( m_flDmgModBullet, FIELD_FLOAT ),
  122. DEFINE_FIELD( m_flDmgModClub, FIELD_FLOAT ),
  123. DEFINE_FIELD( m_flDmgModExplosive, FIELD_FLOAT ),
  124. DEFINE_FIELD( m_flDmgModFire, FIELD_FLOAT ),
  125. DEFINE_FIELD( m_iszPhysicsDamageTableName, FIELD_STRING ),
  126. DEFINE_FIELD( m_iszBreakableModel, FIELD_STRING ),
  127. DEFINE_FIELD( m_iBreakableSkin, FIELD_INTEGER ),
  128. DEFINE_FIELD( m_iBreakableCount, FIELD_INTEGER ),
  129. DEFINE_FIELD( m_iMaxBreakableSize, FIELD_INTEGER ),
  130. DEFINE_FIELD( m_iszBasePropData, FIELD_STRING ),
  131. DEFINE_FIELD( m_iInteractions, FIELD_INTEGER ),
  132. DEFINE_FIELD( m_explodeRadius, FIELD_FLOAT ),
  133. DEFINE_FIELD( m_iszModelName, FIELD_STRING ),
  134. // Physics Influence
  135. DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ),
  136. DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ),
  137. END_DATADESC()
  138. //-----------------------------------------------------------------------------
  139. // Purpose:
  140. //-----------------------------------------------------------------------------
  141. bool CBreakable::KeyValue( const char *szKeyName, const char *szValue )
  142. {
  143. // UNDONE_WC: explicitly ignoring these fields, but they shouldn't be in the map file!
  144. if (FStrEq(szKeyName, "material"))
  145. {
  146. int i = atoi( szValue);
  147. // 0:glass, 1:metal, 2:flesh, 3:wood
  148. if ((i < 0) || (i >= matLastMaterial))
  149. m_Material = matWood;
  150. else
  151. m_Material = (Materials)i;
  152. }
  153. else if (FStrEq(szKeyName, "deadmodel"))
  154. {
  155. }
  156. else if (FStrEq(szKeyName, "shards"))
  157. {
  158. // m_iShards = atof(szValue);
  159. }
  160. else if (FStrEq(szKeyName, "gibmodel") )
  161. {
  162. m_iszGibModel = AllocPooledString(szValue);
  163. }
  164. else if (FStrEq(szKeyName, "spawnobject") )
  165. {
  166. int object = atoi( szValue );
  167. if ( object > 0 && object < ARRAYSIZE(pSpawnObjects) )
  168. m_iszSpawnObject = MAKE_STRING( pSpawnObjects[object] );
  169. }
  170. else if (FStrEq(szKeyName, "propdata") )
  171. {
  172. int pdata = atoi( szValue );
  173. if ( pdata > 0 && pdata < ARRAYSIZE(pFGDPropData) )
  174. {
  175. m_iszPropData = MAKE_STRING( pFGDPropData[pdata] );
  176. }
  177. else if ( pdata )
  178. {
  179. // If you've hit this warning, it's probably because someone's added a new
  180. // propdata field to func_breakables in the .fgd, and not added it to the
  181. // pFGDPropData list.
  182. Warning("func_breakable with invalid propdata %d.\n", pdata );
  183. }
  184. }
  185. else if (FStrEq(szKeyName, "lip") )
  186. {
  187. }
  188. else
  189. return BaseClass::KeyValue( szKeyName, szValue );
  190. return true;
  191. }
  192. //-----------------------------------------------------------------------------
  193. // Purpose:
  194. //-----------------------------------------------------------------------------
  195. void CBreakable::Spawn( void )
  196. {
  197. // Initialize damage modifiers. Must be done before baseclass spawn.
  198. m_flDmgModBullet = func_breakdmg_bullet.GetFloat();
  199. m_flDmgModClub = func_breakdmg_club.GetFloat();
  200. m_flDmgModExplosive = func_breakdmg_explosive.GetFloat();
  201. m_flDmgModFire = 1.0f;
  202. ParsePropData();
  203. Precache( );
  204. if ( !m_iHealth || FBitSet( m_spawnflags, SF_BREAK_TRIGGER_ONLY ) )
  205. {
  206. // This allows people to shoot at the glass (since it's penetrable)
  207. if ( m_Material == matGlass )
  208. {
  209. m_iHealth = 1;
  210. }
  211. m_takedamage = DAMAGE_NO;
  212. }
  213. else
  214. {
  215. m_takedamage = DAMAGE_YES;
  216. }
  217. m_iMaxHealth = ( m_iHealth > 0 ) ? m_iHealth : 1;
  218. SetSolid( SOLID_BSP );
  219. SetMoveType( MOVETYPE_PUSH );
  220. SetModel( STRING( GetModelName() ) );//set size and link into world.
  221. SetTouch( &CBreakable::BreakTouch );
  222. if ( FBitSet( m_spawnflags, SF_BREAK_TRIGGER_ONLY ) ) // Only break on trigger
  223. {
  224. SetTouch( NULL );
  225. }
  226. // Flag unbreakable glass as "worldbrush" so it will block ALL tracelines
  227. if ( !IsBreakable() && m_nRenderMode != kRenderNormal )
  228. AddFlag( FL_WORLDBRUSH );
  229. if ( m_impactEnergyScale == 0 )
  230. {
  231. m_impactEnergyScale = 1.0;
  232. }
  233. CreateVPhysics();
  234. }
  235. //-----------------------------------------------------------------------------
  236. // Purpose: Parse this prop's data, if it has a keyvalues section.
  237. // Returns true only if this prop is using a model that has a prop_data section that's invalid.
  238. //-----------------------------------------------------------------------------
  239. void CBreakable::ParsePropData( void )
  240. {
  241. if ( m_iszPropData == NULL_STRING )
  242. return;
  243. if ( StringHasPrefixCaseSensitive( STRING(m_iszPropData), "None" ) )
  244. return;
  245. g_PropDataSystem.ParsePropFromBase( this, this, STRING(m_iszPropData) );
  246. }
  247. //-----------------------------------------------------------------------------
  248. // Purpose:
  249. //-----------------------------------------------------------------------------
  250. bool CBreakable::CreateVPhysics( void )
  251. {
  252. VPhysicsInitStatic();
  253. return true;
  254. }
  255. //-----------------------------------------------------------------------------
  256. // Purpose:
  257. //-----------------------------------------------------------------------------
  258. const char *CBreakable::MaterialSound( Materials precacheMaterial )
  259. {
  260. switch ( precacheMaterial )
  261. {
  262. case matWood:
  263. return "Breakable.MatWood";
  264. case matFlesh:
  265. return "Breakable.MatFlesh";
  266. case matComputer:
  267. return "Breakable.Computer";
  268. case matUnbreakableGlass:
  269. case matGlass:
  270. return "Breakable.MatGlass";
  271. case matMetalPanel:
  272. case matMetal:
  273. return "Breakable.MatMetal";
  274. case matCinderBlock:
  275. case matRocks:
  276. return "Breakable.MatConcrete";
  277. case matCeilingTile:
  278. case matNone:
  279. default:
  280. break;
  281. }
  282. return NULL;
  283. }
  284. void CBreakable::MaterialSoundRandom( int entindex, Materials soundMaterial, float volume )
  285. {
  286. const char *soundname;
  287. soundname = MaterialSound( soundMaterial );
  288. if ( !soundname )
  289. return;
  290. CSoundParameters params;
  291. if ( !GetParametersForSound( soundname, params, NULL ) )
  292. return;
  293. CPASAttenuationFilter filter( CBaseEntity::Instance( entindex ), params.soundlevel );
  294. EmitSound_t ep;
  295. ep.m_nChannel = params.channel;
  296. ep.m_pSoundName = params.soundname;
  297. ep.m_flVolume = volume;
  298. ep.m_SoundLevel = params.soundlevel;
  299. EmitSound( filter, entindex, ep );
  300. }
  301. void CBreakable::Precache( void )
  302. {
  303. const char *pGibName = "WoodChunks";
  304. switch (m_Material)
  305. {
  306. case matWood:
  307. pGibName = "WoodChunks";
  308. break;
  309. case matUnbreakableGlass:
  310. case matGlass:
  311. pGibName = "GlassChunks";
  312. break;
  313. case matMetalPanel:
  314. pGibName = "MetalPanelChunks";
  315. break;
  316. case matMetal:
  317. pGibName = "MetalChunks";
  318. break;
  319. case matRocks:
  320. pGibName = "ConcreteChunks";
  321. break;
  322. case matCinderBlock:
  323. pGibName = "ConcreteChunks";
  324. break;
  325. #if HL2_EPISODIC
  326. case matNone:
  327. pGibName = "";
  328. break;
  329. #endif
  330. default:
  331. Warning("%s (%s) at (%.3f %.3f %.3f) using obsolete or unknown material type.\n", GetClassname(), GetDebugName(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
  332. pGibName = "WoodChunks";
  333. break;
  334. }
  335. if ( m_iszGibModel != NULL_STRING )
  336. {
  337. pGibName = STRING(m_iszGibModel);
  338. }
  339. m_iszModelName = MAKE_STRING( pGibName );
  340. // Precache the spawn item's data
  341. if ( !CommandLine()->CheckParm("-makereslists"))
  342. {
  343. if ( m_iszSpawnObject != NULL_STRING )
  344. {
  345. UTIL_PrecacheOther( STRING( m_iszSpawnObject ) );
  346. }
  347. }
  348. else
  349. {
  350. // Actually, precache all possible objects...
  351. for ( int i = 0; i < ARRAYSIZE(pSpawnObjects) ; ++i )
  352. {
  353. if ( !pSpawnObjects[ i ] )
  354. continue;
  355. if ( !Q_strnicmp( pSpawnObjects[ i ], "unused", Q_strlen( "unused" ) ) )
  356. continue;
  357. UTIL_PrecacheOther( pSpawnObjects[ i ] );
  358. }
  359. }
  360. PrecacheScriptSound( "Breakable.MatGlass" );
  361. PrecacheScriptSound( "Breakable.MatWood" );
  362. PrecacheScriptSound( "Breakable.MatMetal" );
  363. PrecacheScriptSound( "Breakable.MatFlesh" );
  364. PrecacheScriptSound( "Breakable.MatConcrete" );
  365. PrecacheScriptSound( "Breakable.Computer" );
  366. PrecacheScriptSound( "Breakable.Crate" );
  367. PrecacheScriptSound( "Breakable.Glass" );
  368. PrecacheScriptSound( "Breakable.Metal" );
  369. PrecacheScriptSound( "Breakable.Flesh" );
  370. PrecacheScriptSound( "Breakable.Concrete" );
  371. PrecacheScriptSound( "Breakable.Ceiling" );
  372. }
  373. // play shard sound when func_breakable takes damage.
  374. // the more damage, the louder the shard sound.
  375. void CBreakable::DamageSound( void )
  376. {
  377. int pitch;
  378. float fvol;
  379. char *soundname = NULL;
  380. int material = m_Material;
  381. if (random->RandomInt(0,2))
  382. {
  383. pitch = PITCH_NORM;
  384. }
  385. else
  386. {
  387. pitch = 95 + random->RandomInt(0,34);
  388. }
  389. fvol = random->RandomFloat(0.75, 1.0);
  390. if (material == matComputer && random->RandomInt(0,1))
  391. {
  392. material = matMetal;
  393. }
  394. switch (material)
  395. {
  396. case matGlass:
  397. case matUnbreakableGlass:
  398. soundname = "Breakable.MatGlass";
  399. break;
  400. case matWood:
  401. soundname = "Breakable.MatWood";
  402. break;
  403. case matMetalPanel:
  404. case matMetal:
  405. soundname = "Breakable.MatMetal";
  406. break;
  407. case matRocks:
  408. case matCinderBlock:
  409. soundname = "Breakable.MatConcrete";
  410. break;
  411. case matComputer:
  412. soundname = "Breakable.Computer";
  413. break;
  414. default:
  415. break;
  416. }
  417. if ( soundname )
  418. {
  419. CSoundParameters params;
  420. if ( GetParametersForSound( soundname, params, NULL ) )
  421. {
  422. CPASAttenuationFilter filter( this );
  423. EmitSound_t ep;
  424. ep.m_nChannel = params.channel;
  425. ep.m_pSoundName = params.soundname;
  426. ep.m_flVolume = fvol;
  427. ep.m_SoundLevel = params.soundlevel;
  428. ep.m_nPitch = pitch;
  429. EmitSound( filter, entindex(), ep );
  430. }
  431. }
  432. }
  433. void CBreakable::BreakTouch( CBaseEntity *pOther )
  434. {
  435. float flDamage;
  436. // only players can break these right now
  437. if ( !pOther->IsPlayer() || !IsBreakable() )
  438. {
  439. return;
  440. }
  441. // can I be broken when run into?
  442. if ( HasSpawnFlags( SF_BREAK_TOUCH ) )
  443. {
  444. flDamage = pOther->GetSmoothedVelocity().Length() * 0.01;
  445. if (flDamage >= m_iHealth)
  446. {
  447. m_takedamage = DAMAGE_YES;
  448. SetTouch( NULL );
  449. OnTakeDamage( CTakeDamageInfo( pOther, pOther, flDamage, DMG_CRUSH ) );
  450. // do a little damage to player if we broke glass or computer
  451. CTakeDamageInfo info( pOther, pOther, flDamage/4, DMG_SLASH );
  452. CalculateMeleeDamageForce( &info, (pOther->GetAbsOrigin() - GetAbsOrigin()), GetAbsOrigin() );
  453. pOther->TakeDamage( info );
  454. }
  455. }
  456. // can I be broken when stood upon?
  457. if ( HasSpawnFlags( SF_BREAK_PRESSURE ) && pOther->GetGroundEntity() == this )
  458. {
  459. // play creaking sound here.
  460. DamageSound();
  461. m_hBreaker = pOther;
  462. SetThink ( &CBreakable::Die );
  463. SetTouch( NULL );
  464. // Add optional delay
  465. SetNextThink( gpGlobals->curtime + m_flPressureDelay );
  466. }
  467. }
  468. //-----------------------------------------------------------------------------
  469. // Purpose: Input handler for adding to the breakable's health.
  470. // Input : Integer health points to add.
  471. //-----------------------------------------------------------------------------
  472. void CBreakable::InputAddHealth( inputdata_t &inputdata )
  473. {
  474. UpdateHealth( m_iHealth + inputdata.value.Int(), inputdata.pActivator );
  475. }
  476. //-----------------------------------------------------------------------------
  477. // Purpose: Input handler for breaking the breakable immediately.
  478. //-----------------------------------------------------------------------------
  479. void CBreakable::InputBreak( inputdata_t &inputdata )
  480. {
  481. Break( inputdata.pActivator );
  482. }
  483. //-----------------------------------------------------------------------------
  484. // Purpose: Input handler for removing health from the breakable.
  485. // Input : Integer health points to remove.
  486. //-----------------------------------------------------------------------------
  487. void CBreakable::InputRemoveHealth( inputdata_t &inputdata )
  488. {
  489. UpdateHealth( m_iHealth - inputdata.value.Int(), inputdata.pActivator );
  490. }
  491. //-----------------------------------------------------------------------------
  492. // Purpose: Input handler for setting the breakable's health.
  493. //-----------------------------------------------------------------------------
  494. void CBreakable::InputSetHealth( inputdata_t &inputdata )
  495. {
  496. UpdateHealth( inputdata.value.Int(), inputdata.pActivator );
  497. }
  498. //-----------------------------------------------------------------------------
  499. // Purpose: Input handler for setting the breakable's mass.
  500. //-----------------------------------------------------------------------------
  501. void CBreakable::InputSetMass( inputdata_t &inputdata )
  502. {
  503. IPhysicsObject * vPhys = VPhysicsGetObject();
  504. if ( vPhys )
  505. {
  506. float toMass = inputdata.value.Float();
  507. Assert(toMass > 0);
  508. vPhys->SetMass( toMass );
  509. }
  510. else
  511. {
  512. Warning( "Tried to call SetMass() on %s but it has no physics.\n", GetEntityName().ToCStr() );
  513. }
  514. }
  515. //-----------------------------------------------------------------------------
  516. // Purpose: Choke point for changes to breakable health. Ensures outputs are fired.
  517. // Input : iNewHealth -
  518. // pActivator -
  519. // Output : Returns true if the breakable survived, false if it died (broke).
  520. //-----------------------------------------------------------------------------
  521. bool CBreakable::UpdateHealth( int iNewHealth, CBaseEntity *pActivator )
  522. {
  523. if ( iNewHealth != m_iHealth )
  524. {
  525. m_iHealth = iNewHealth;
  526. if ( m_iMaxHealth == 0 )
  527. {
  528. Assert( false );
  529. m_iMaxHealth = 1;
  530. }
  531. // Output the new health as a percentage of max health [0..1]
  532. float flRatio = clamp( (float)m_iHealth / (float)m_iMaxHealth, 0, 1 );
  533. m_OnHealthChanged.Set( flRatio, pActivator, this );
  534. if ( m_iHealth <= 0 )
  535. {
  536. Break( pActivator );
  537. return false;
  538. }
  539. else
  540. {
  541. if ( FBitSet( m_spawnflags, SF_BREAK_TRIGGER_ONLY ) )
  542. {
  543. m_takedamage = DAMAGE_NO;
  544. }
  545. else
  546. {
  547. m_takedamage = DAMAGE_YES;
  548. }
  549. }
  550. }
  551. return true;
  552. }
  553. //-----------------------------------------------------------------------------
  554. // Purpose: Breaks the breakable if it can be broken.
  555. // Input : pBreaker - The entity that caused us to break, either via an input,
  556. // by shooting us, or by touching us.
  557. //-----------------------------------------------------------------------------
  558. void CBreakable::Break( CBaseEntity *pBreaker )
  559. {
  560. if ( IsBreakable() )
  561. {
  562. m_hBreaker = pBreaker;
  563. Die();
  564. }
  565. }
  566. void CBreakable::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr )
  567. {
  568. // random spark if this is a 'computer' object
  569. if (random->RandomInt(0,1) )
  570. {
  571. switch( m_Material )
  572. {
  573. case matComputer:
  574. {
  575. g_pEffects->Sparks( ptr->endpos );
  576. EmitSound( "Breakable.Computer" );
  577. }
  578. break;
  579. case matUnbreakableGlass:
  580. g_pEffects->Ricochet( ptr->endpos, (vecDir*-1.0f) );
  581. break;
  582. }
  583. }
  584. BaseClass::TraceAttack( info, vecDir, ptr );
  585. }
  586. //-----------------------------------------------------------------------------
  587. // Purpose: Allows us to take damage from physics objects
  588. //-----------------------------------------------------------------------------
  589. void CBreakable::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
  590. {
  591. BaseClass::VPhysicsCollision( index, pEvent );
  592. Vector damagePos;
  593. pEvent->pInternalData->GetContactPoint( damagePos );
  594. Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass();
  595. if ( damageForce == vec3_origin )
  596. {
  597. // This can happen if this entity is a func_breakable, and can't move.
  598. // Use the velocity of the entity that hit us instead.
  599. damageForce = pEvent->postVelocity[!index] * pEvent->pObjects[!index]->GetMass();
  600. }
  601. // If we're supposed to explode on collision, do so
  602. if ( HasSpawnFlags( SF_BREAK_PHYSICS_BREAK_IMMEDIATELY ) )
  603. {
  604. // We're toast
  605. m_bTookPhysicsDamage = true;
  606. CBaseEntity *pHitEntity = pEvent->pEntities[!index];
  607. // HACKHACK: Reset mass to get correct collision response for the object breaking this glass
  608. if ( m_Material == matGlass )
  609. {
  610. pEvent->pObjects[index]->SetMass( 2.0f );
  611. }
  612. CTakeDamageInfo dmgInfo( pHitEntity, pHitEntity, damageForce, damagePos, (m_iHealth + 1), DMG_CRUSH );
  613. PhysCallbackDamage( this, dmgInfo, *pEvent, index );
  614. }
  615. else if ( !HasSpawnFlags( SF_BREAK_DONT_TAKE_PHYSICS_DAMAGE ) )
  616. {
  617. int otherIndex = !index;
  618. CBaseEntity *pOther = pEvent->pEntities[otherIndex];
  619. // We're to take normal damage from this
  620. int damageType;
  621. IBreakableWithPropData *pBreakableInterface = assert_cast<IBreakableWithPropData*>(this);
  622. float damage = CalculateDefaultPhysicsDamage( index, pEvent, m_impactEnergyScale, true, damageType, pBreakableInterface->GetPhysicsDamageTable() );
  623. if ( damage > 0 )
  624. {
  625. // HACKHACK: Reset mass to get correct collision response for the object breaking this glass
  626. if ( m_Material == matGlass )
  627. {
  628. pEvent->pObjects[index]->SetMass( 2.0f );
  629. }
  630. CTakeDamageInfo dmgInfo( pOther, pOther, damageForce, damagePos, damage, damageType );
  631. PhysCallbackDamage( this, dmgInfo, *pEvent, index );
  632. }
  633. }
  634. }
  635. //-----------------------------------------------------------------------------
  636. // Purpose: Allows us to make damage exceptions that are breakable-specific.
  637. //-----------------------------------------------------------------------------
  638. int CBreakable::OnTakeDamage( const CTakeDamageInfo &info )
  639. {
  640. Vector vecTemp;
  641. CTakeDamageInfo subInfo = info;
  642. // If attacker can't do at least the min required damage to us, don't take any damage from them
  643. if ( m_takedamage == DAMAGE_NO || info.GetDamage() < m_iMinHealthDmg )
  644. return 0;
  645. // Check our damage filter
  646. if ( !PassesDamageFilter(subInfo) )
  647. {
  648. m_bTookPhysicsDamage = false;
  649. return 1;
  650. }
  651. vecTemp = subInfo.GetInflictor()->GetAbsOrigin() - WorldSpaceCenter();
  652. if (!IsBreakable())
  653. return 0;
  654. float flPropDamage = GetBreakableDamage( subInfo, this );
  655. subInfo.SetDamage( flPropDamage );
  656. int iPrevHealth = m_iHealth;
  657. BaseClass::OnTakeDamage( subInfo );
  658. // HACK: slam health back to what it was so UpdateHealth can do its thing
  659. int iNewHealth = m_iHealth;
  660. m_iHealth = iPrevHealth;
  661. if ( !UpdateHealth( iNewHealth, info.GetAttacker() ) )
  662. return 1;
  663. // Make a shard noise each time func breakable is hit, if it's capable of taking damage and it took damage
  664. if ( m_takedamage == DAMAGE_YES && m_iHealth < iPrevHealth )
  665. {
  666. // Don't play shard noise if being burned.
  667. // Don't play shard noise if cbreakable actually died.
  668. if ( ( subInfo.GetDamageType() & DMG_BURN ) == false )
  669. {
  670. DamageSound();
  671. }
  672. }
  673. return 1;
  674. }
  675. //------------------------------------------------------------------------------
  676. // Purpose : Reset the OnGround flags for any entities that may have been
  677. // resting on me
  678. // Input :
  679. // Output :
  680. //------------------------------------------------------------------------------
  681. void CBreakable::ResetOnGroundFlags(void)
  682. {
  683. // !!! HACK This should work!
  684. // Build a box above the entity that looks like an 9 inch high sheet
  685. Vector mins, maxs;
  686. CollisionProp()->WorldSpaceAABB( &mins, &maxs );
  687. mins.z -= 1;
  688. maxs.z += 8;
  689. // BUGBUG -- can only find 256 entities on a breakable -- should be enough
  690. CBaseEntity *pList[256];
  691. int count = UTIL_EntitiesInBox( pList, 256, mins, maxs, FL_ONGROUND );
  692. if ( count )
  693. {
  694. for ( int i = 0; i < count; i++ )
  695. {
  696. pList[i]->SetGroundEntity( (CBaseEntity *)NULL );
  697. }
  698. }
  699. #ifdef PORTAL
  700. // !!! HACK This should work!
  701. // Tell touching portals to fizzle
  702. int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
  703. if( iPortalCount != 0 )
  704. {
  705. Vector vMin, vMax;
  706. CollisionProp()->WorldSpaceAABB( &vMin, &vMax );
  707. Vector vBoxCenter = ( vMin + vMax ) * 0.5f;
  708. Vector vBoxExtents = ( vMax - vMin ) * 0.5f;
  709. CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
  710. for( int i = 0; i != iPortalCount; ++i )
  711. {
  712. CProp_Portal *pTempPortal = pPortals[i];
  713. if( UTIL_IsBoxIntersectingPortal( vBoxCenter, vBoxExtents, pTempPortal ) )
  714. {
  715. pTempPortal->DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
  716. pTempPortal->Fizzle();
  717. }
  718. }
  719. }
  720. #endif
  721. }
  722. //-----------------------------------------------------------------------------
  723. // Purpose: Breaks the breakable. m_hBreaker is the entity that caused us to break.
  724. //-----------------------------------------------------------------------------
  725. void CBreakable::Die( void )
  726. {
  727. Vector vecVelocity;// shard velocity
  728. char cFlag = 0;
  729. int pitch;
  730. float fvol;
  731. pitch = 95 + random->RandomInt(0,29);
  732. if (pitch > 97 && pitch < 103)
  733. {
  734. pitch = 100;
  735. }
  736. // The more negative m_iHealth, the louder
  737. // the sound should be.
  738. fvol = random->RandomFloat(0.85, 1.0) + (abs(m_iHealth.Get()) / 100.0);
  739. if (fvol > 1.0)
  740. {
  741. fvol = 1.0;
  742. }
  743. const char *soundname = NULL;
  744. switch (m_Material)
  745. {
  746. default:
  747. break;
  748. case matGlass:
  749. soundname = "Breakable.Glass";
  750. cFlag = BREAK_GLASS;
  751. break;
  752. case matWood:
  753. soundname = "Breakable.Crate";
  754. cFlag = BREAK_WOOD;
  755. break;
  756. case matComputer:
  757. soundname = "Breakable.Computer";
  758. cFlag = BREAK_METAL;
  759. break;
  760. case matMetal:
  761. case matMetalPanel:
  762. soundname = "Breakable.Metal";
  763. cFlag = BREAK_METAL;
  764. break;
  765. case matFlesh:
  766. soundname = "Breakable.Flesh";
  767. cFlag = BREAK_FLESH;
  768. break;
  769. case matRocks:
  770. case matCinderBlock:
  771. soundname = "Breakable.Concrete";
  772. cFlag = BREAK_CONCRETE;
  773. break;
  774. case matCeilingTile:
  775. soundname = "Breakable.Ceiling";
  776. break;
  777. }
  778. if ( soundname )
  779. {
  780. if ( m_hBreaker && m_hBreaker->IsPlayer() )
  781. {
  782. IGameEvent * event = gameeventmanager->CreateEvent( "break_breakable" );
  783. if ( event )
  784. {
  785. event->SetInt( "userid", ToBasePlayer( m_hBreaker )->GetUserID() );
  786. event->SetInt( "entindex", entindex() );
  787. event->SetInt( "material", cFlag );
  788. gameeventmanager->FireEvent( event );
  789. }
  790. }
  791. CSoundParameters params;
  792. if ( GetParametersForSound( soundname, params, NULL ) )
  793. {
  794. CPASAttenuationFilter filter( this );
  795. EmitSound_t ep;
  796. ep.m_nChannel = params.channel;
  797. ep.m_pSoundName = params.soundname;
  798. ep.m_flVolume = fvol;
  799. ep.m_SoundLevel = params.soundlevel;
  800. ep.m_nPitch = pitch;
  801. EmitSound( filter, entindex(), ep );
  802. }
  803. }
  804. switch( m_Explosion )
  805. {
  806. case expDirected:
  807. vecVelocity = g_vecAttackDir * -200;
  808. break;
  809. case expUsePrecise:
  810. {
  811. AngleVectors( m_GibDir, &vecVelocity, NULL, NULL );
  812. vecVelocity *= 200;
  813. }
  814. break;
  815. case expRandom:
  816. vecVelocity.x = 0;
  817. vecVelocity.y = 0;
  818. vecVelocity.z = 0;
  819. break;
  820. default:
  821. DevMsg("**ERROR - Unspecified gib dir method in func_breakable!\n");
  822. break;
  823. }
  824. Vector vecSpot = WorldSpaceCenter();
  825. CPVSFilter filter2( vecSpot );
  826. int iModelIndex = 0;
  827. CCollisionProperty *pCollisionProp = CollisionProp();
  828. Vector vSize = pCollisionProp->OBBSize();
  829. int iCount = ( vSize[0] * vSize[1] + vSize[1] * vSize[2] + vSize[2] * vSize[0] ) / ( 3 * 12 * 12 );
  830. if ( iCount > func_break_max_pieces.GetInt() )
  831. {
  832. iCount = func_break_max_pieces.GetInt();
  833. }
  834. if ( !breakable_disable_gib_limit.GetBool() && iCount )
  835. {
  836. if ( m_PerformanceMode == PM_NO_GIBS )
  837. {
  838. iCount = 0;
  839. }
  840. else if ( m_PerformanceMode == PM_REDUCED_GIBS )
  841. {
  842. int iNewCount = iCount * func_break_reduction_factor.GetFloat();
  843. iCount = MAX( iNewCount, 1 );
  844. }
  845. }
  846. if ( m_iszModelName != NULL_STRING )
  847. {
  848. for ( int i = 0; i < iCount; i++ )
  849. {
  850. iModelIndex = modelinfo->GetModelIndex( g_PropDataSystem.GetRandomChunkModel( STRING( m_iszModelName ) ) );
  851. // All objects except the first one in this run are marked as slaves...
  852. int slaveFlag = 0;
  853. if ( i != 0 )
  854. {
  855. slaveFlag = BREAK_SLAVE;
  856. }
  857. te->BreakModel( filter2, 0.0,
  858. vecSpot, pCollisionProp->GetCollisionAngles(), vSize,
  859. vecVelocity, iModelIndex, 100, 1, 2.5, cFlag | slaveFlag );
  860. }
  861. }
  862. ResetOnGroundFlags();
  863. // Don't fire something that could fire myself
  864. SetName( NULL_STRING );
  865. AddSolidFlags( FSOLID_NOT_SOLID );
  866. // Fire targets on break
  867. m_OnBreak.FireOutput( m_hBreaker, this );
  868. VPhysicsDestroyObject();
  869. SetThink( &CBreakable::SUB_Remove );
  870. SetNextThink( gpGlobals->curtime + 0.1f );
  871. if ( m_iszSpawnObject != NULL_STRING )
  872. {
  873. CBaseEntity::Create( STRING(m_iszSpawnObject), vecSpot, pCollisionProp->GetCollisionAngles(), this );
  874. }
  875. if ( Explodable() )
  876. {
  877. ExplosionCreate( vecSpot, pCollisionProp->GetCollisionAngles(), this, GetExplosiveDamage(), GetExplosiveRadius(), true );
  878. }
  879. }
  880. //-----------------------------------------------------------------------------
  881. // Purpose: Returns whether this object can be broken.
  882. //-----------------------------------------------------------------------------
  883. bool CBreakable::IsBreakable( void )
  884. {
  885. return m_Material != matUnbreakableGlass;
  886. }
  887. //-----------------------------------------------------------------------------
  888. // Purpose:
  889. //-----------------------------------------------------------------------------
  890. char const *CBreakable::DamageDecal( int bitsDamageType, int gameMaterial )
  891. {
  892. if ( m_Material == matGlass )
  893. return "GlassBreak";
  894. if ( m_Material == matUnbreakableGlass )
  895. return "BulletProof";
  896. return BaseClass::DamageDecal( bitsDamageType, gameMaterial );
  897. }
  898. //-----------------------------------------------------------------------------
  899. // Purpose: Draw any debug text overlays
  900. // Output : Current text offset from the top
  901. //-----------------------------------------------------------------------------
  902. int CBreakable::DrawDebugTextOverlays(void)
  903. {
  904. int text_offset = BaseClass::DrawDebugTextOverlays();
  905. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  906. {
  907. if ( GetMaxHealth() )
  908. {
  909. char tempstr[512];
  910. Q_snprintf(tempstr,sizeof(tempstr),"Health: %i",GetHealth());
  911. EntityText(text_offset,tempstr,0);
  912. text_offset++;
  913. }
  914. if ( m_iszBasePropData != NULL_STRING )
  915. {
  916. char tempstr[512];
  917. Q_snprintf(tempstr, sizeof(tempstr),"Base PropData: %s", STRING(m_iszBasePropData) );
  918. EntityText( text_offset, tempstr, 0);
  919. text_offset++;
  920. }
  921. }
  922. return text_offset;
  923. }
  924. //-----------------------------------------------------------------------------
  925. // Purpose: Keep track of physgun influence
  926. //-----------------------------------------------------------------------------
  927. void CBreakable::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
  928. {
  929. m_hPhysicsAttacker = pPhysGunUser;
  930. m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
  931. }
  932. void CBreakable::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t reason )
  933. {
  934. m_hPhysicsAttacker = pPhysGunUser;
  935. m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
  936. }
  937. CBasePlayer *CBreakable::HasPhysicsAttacker( float dt )
  938. {
  939. if (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime)
  940. {
  941. return m_hPhysicsAttacker;
  942. }
  943. return NULL;
  944. }
  945. //=============================================================================================================================
  946. // PUSHABLE
  947. //=============================================================================================================================
  948. //-----------------------------------------------------------------------------
  949. // Purpose:
  950. //-----------------------------------------------------------------------------
  951. class CPushable : public CBreakable
  952. {
  953. public:
  954. DECLARE_CLASS( CPushable, CBreakable );
  955. void Spawn ( void );
  956. bool CreateVPhysics( void );
  957. void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
  958. virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() | FCAP_ONOFF_USE; }
  959. // breakables use an overridden takedamage
  960. virtual int OnTakeDamage( const CTakeDamageInfo &info );
  961. virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
  962. unsigned int PhysicsSolidMaskForEntity( void ) const { return MASK_PLAYERSOLID; }
  963. };
  964. LINK_ENTITY_TO_CLASS( func_pushable, CPushable );
  965. void CPushable::Spawn( void )
  966. {
  967. if ( HasSpawnFlags( SF_PUSH_BREAKABLE ) )
  968. {
  969. BaseClass::Spawn();
  970. }
  971. else
  972. {
  973. Precache();
  974. SetSolid( SOLID_VPHYSICS );
  975. SetMoveType( MOVETYPE_PUSH );
  976. SetModel( STRING( GetModelName() ) );
  977. CreateVPhysics();
  978. }
  979. }
  980. bool CPushable::CreateVPhysics( void )
  981. {
  982. VPhysicsInitNormal( SOLID_VPHYSICS, 0, false );
  983. IPhysicsObject *pPhysObj = VPhysicsGetObject();
  984. if ( pPhysObj )
  985. {
  986. pPhysObj->SetMass( 30 );
  987. // Vector vecInertia = Vector(800, 800, 800);
  988. // pPhysObj->SetInertia( vecInertia );
  989. }
  990. return true;
  991. }
  992. // Pull the func_pushable
  993. void CPushable::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  994. {
  995. BaseClass::Use( pActivator, pCaller, useType, value );
  996. }
  997. int CPushable::OnTakeDamage( const CTakeDamageInfo &info )
  998. {
  999. if ( m_spawnflags & SF_PUSH_BREAKABLE )
  1000. return BaseClass::OnTakeDamage( info );
  1001. return 1;
  1002. }
  1003. //-----------------------------------------------------------------------------
  1004. // Purpose: Allows us to take damage from physics objects
  1005. //-----------------------------------------------------------------------------
  1006. void CPushable::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
  1007. {
  1008. int otherIndex = !index;
  1009. CBaseEntity *pOther = pEvent->pEntities[otherIndex];
  1010. if ( pOther->IsPlayer() )
  1011. {
  1012. // Pushables don't take damage from impacts with the player
  1013. // We call all the way back to the baseclass to get the physics effects.
  1014. CBaseEntity::VPhysicsCollision( index, pEvent );
  1015. return;
  1016. }
  1017. BaseClass::VPhysicsCollision( index, pEvent );
  1018. }