Team Fortress 2 Source Code as on 22/4/2020
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.

1662 lines
49 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: static_prop - don't move, don't animate, don't do anything.
  4. // physics_prop - move, take damage, but don't animate
  5. //
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "props_shared.h"
  9. #include "filesystem.h"
  10. #include "animation.h"
  11. #include <vcollide_parse.h>
  12. #include <bone_setup.h>
  13. #include <tier0/vprof.h>
  14. #ifdef CLIENT_DLL
  15. #include "gamestringpool.h"
  16. #endif
  17. // memdbgon must be the last include file in a .cpp file!!!
  18. #include "tier0/memdbgon.h"
  19. ConVar sv_pushaway_clientside_size( "sv_pushaway_clientside_size", "15", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Minimum size of pushback objects" );
  20. ConVar props_break_max_pieces( "props_break_max_pieces", "-1", 0, "Maximum prop breakable piece count (-1 = model default)" );
  21. ConVar props_break_max_pieces_perframe( "props_break_max_pieces_perframe", "-1", FCVAR_REPLICATED, "Maximum prop breakable piece count per frame (-1 = model default)" );
  22. #ifdef GAME_DLL
  23. extern ConVar breakable_multiplayer;
  24. #else
  25. ConVar cl_burninggibs( "cl_burninggibs", "0", 0, "A burning player that gibs has burning gibs." );
  26. #endif // GAME_DLL
  27. extern bool PropBreakableCapEdictsOnCreateAll(int modelindex, IPhysicsObject *pPhysics, const breakablepropparams_t &params, CBaseEntity *pEntity, int iPrecomputedBreakableCount = -1 );
  28. extern CBaseEntity *BreakModelCreateSingle( CBaseEntity *pOwner, breakmodel_t *pModel, const Vector &position,
  29. const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, int nSkin, const breakablepropparams_t &params );
  30. static int nPropBreakablesPerFrameCount = 0;
  31. static int nFrameNumber = 0;
  32. //=============================================================================================================
  33. // UTILITY FUNCS
  34. //=============================================================================================================
  35. //-----------------------------------------------------------------------------
  36. // Purpose: returns the axis index with the greatest size
  37. // Input : &vec -
  38. // Output : static int
  39. //-----------------------------------------------------------------------------
  40. static int GreatestAxis( const Vector &vec )
  41. {
  42. if ( vec.x > vec.y )
  43. {
  44. if ( vec.x > vec.z )
  45. return 0;
  46. return 2;
  47. }
  48. if ( vec.y > vec.z )
  49. return 1;
  50. return 2;
  51. }
  52. //-----------------------------------------------------------------------------
  53. // Purpose: returns the axis index with the smallest size
  54. // Input : &vec -
  55. // Output : static int
  56. //-----------------------------------------------------------------------------
  57. static int SmallestAxis( const Vector &vec )
  58. {
  59. if ( vec.x < vec.y )
  60. {
  61. if ( vec.x < vec.z )
  62. return 0;
  63. return 2;
  64. }
  65. if ( vec.y < vec.z )
  66. return 1;
  67. return 2;
  68. }
  69. //-----------------------------------------------------------------------------
  70. // Purpose: Rotates a matrix by 90 degrees in the plane of axis0/axis1
  71. // Input : &matrix -
  72. // axis0 -
  73. // axis1 -
  74. // Output : static void
  75. //-----------------------------------------------------------------------------
  76. static void MatrixRot90( matrix3x4_t &matrix, int axis0, int axis1 )
  77. {
  78. Vector col0, col1;
  79. MatrixGetColumn( matrix, axis0, col0 );
  80. MatrixGetColumn( matrix, axis1, col1 );
  81. MatrixSetColumn( col1, axis0, matrix );
  82. MatrixSetColumn( -col0, axis1, matrix );
  83. }
  84. //-----------------------------------------------------------------------------
  85. // Purpose: Given two symmetric boxes, rotate the coordinate frame by the necessary
  86. // 90 degree rotations to approximately align them
  87. // Input : *pInOutMatrix -
  88. // &boxExtents1 -
  89. // &boxExtents2 -
  90. //-----------------------------------------------------------------------------
  91. static void AlignBoxes( matrix3x4_t *pInOutMatrix, const Vector &boxExtents1, const Vector &boxExtents2 )
  92. {
  93. int rotCount = 0;
  94. struct
  95. {
  96. int axis0;
  97. int axis1;
  98. } rotations[2];
  99. Vector ext1 = boxExtents1;
  100. Vector ext2 = boxExtents2;
  101. int axis0 = GreatestAxis( ext1 );
  102. int axis1 = GreatestAxis( ext2 );
  103. if ( axis0 != axis1 )
  104. {
  105. rotations[rotCount].axis0 = axis0;
  106. rotations[rotCount].axis1 = axis1;
  107. rotCount++;
  108. ext2[axis1] = ext2[axis0];
  109. }
  110. ext1[axis0] = 0;
  111. ext2[axis0] = 0;
  112. axis0 = GreatestAxis(ext1);
  113. axis1 = GreatestAxis(ext2);
  114. if ( axis0 != axis1 )
  115. {
  116. rotations[rotCount].axis0 = axis0;
  117. rotations[rotCount].axis1 = axis1;
  118. rotCount++;
  119. }
  120. while ( rotCount > 0 )
  121. {
  122. rotCount--;
  123. MatrixRot90( *pInOutMatrix, rotations[rotCount].axis0, rotations[rotCount].axis1 );
  124. }
  125. }
  126. //=============================================================================================================
  127. // PROP DATA
  128. //=============================================================================================================
  129. CPropData g_PropDataSystem;
  130. // Parsing details for each of the propdata interactions
  131. struct propdata_interaction_s
  132. {
  133. const char *pszSectionName;
  134. const char *pszKeyName;
  135. const char *pszValue;
  136. };
  137. #if !defined(_STATIC_LINKED) || defined(CLIENT_DLL)
  138. propdata_interaction_s sPropdataInteractionSections[PROPINTER_NUM_INTERACTIONS] =
  139. {
  140. { "physgun_interactions", "onworldimpact", "stick" }, // PROPINTER_PHYSGUN_WORLD_STICK,
  141. { "physgun_interactions", "onfirstimpact", "break" }, // PROPINTER_PHYSGUN_FIRST_BREAK,
  142. { "physgun_interactions", "onfirstimpact", "paintsplat" }, // PROPINTER_PHYSGUN_FIRST_PAINT,
  143. { "physgun_interactions", "onfirstimpact", "impale" }, // PROPINTER_PHYSGUN_FIRST_IMPALE,
  144. { "physgun_interactions", "onlaunch", "spin_none" }, // PROPINTER_PHYSGUN_LAUNCH_SPIN_NONE,
  145. { "physgun_interactions", "onlaunch", "spin_zaxis" }, // PROPINTER_PHYSGUN_LAUNCH_SPIN_Z,
  146. { "physgun_interactions", "onbreak", "explode_fire" }, // PROPINTER_PHYSGUN_BREAK_EXPLODE,
  147. { "physgun_interactions", "damage", "none" }, // PROPINTER_PHYSGUN_DAMAGE_NONE,
  148. { "fire_interactions", "flammable", "yes" }, // PROPINTER_FIRE_FLAMMABLE,
  149. { "fire_interactions", "explosive_resist", "yes" }, // PROPINTER_FIRE_EXPLOSIVE_RESIST,
  150. { "fire_interactions", "ignite", "halfhealth" }, // PROPINTER_FIRE_IGNITE_HALFHEALTH,
  151. { "physgun_interactions", "onpickup", "create_flare" }, // PROPINTER_PHYSGUN_CREATE_FLARE,
  152. { "physgun_interactions", "allow_overhead", "yes" }, // PROPINTER_PHYSGUN_ALLOW_OVERHEAD,
  153. { "world_interactions", "onworldimpact", "bloodsplat" }, // PROPINTER_WORLD_BLOODSPLAT,
  154. };
  155. #else
  156. extern propdata_interaction_s sPropdataInteractionSections[PROPINTER_NUM_INTERACTIONS];
  157. #endif
  158. //-----------------------------------------------------------------------------
  159. // Constructor, destructor
  160. //-----------------------------------------------------------------------------
  161. CPropData::CPropData( void ) :
  162. CAutoGameSystem( "CPropData" )
  163. {
  164. m_bPropDataLoaded = false;
  165. m_pKVPropData = NULL;
  166. }
  167. //-----------------------------------------------------------------------------
  168. // Inherited from IAutoServerSystem
  169. //-----------------------------------------------------------------------------
  170. void CPropData::LevelInitPreEntity( void )
  171. {
  172. m_BreakableChunks.RemoveAll();
  173. ParsePropDataFile();
  174. }
  175. //-----------------------------------------------------------------------------
  176. // Purpose:
  177. //-----------------------------------------------------------------------------
  178. void CPropData::LevelShutdownPostEntity( void )
  179. {
  180. if ( m_pKVPropData )
  181. {
  182. m_pKVPropData->deleteThis();
  183. m_pKVPropData = NULL;
  184. }
  185. }
  186. //-----------------------------------------------------------------------------
  187. // Clear out the stats + their history
  188. //-----------------------------------------------------------------------------
  189. void CPropData::ParsePropDataFile( void )
  190. {
  191. m_pKVPropData = new KeyValues( "PropDatafile" );
  192. if ( !m_pKVPropData->LoadFromFile( filesystem, "scripts/propdata.txt" ) )
  193. {
  194. m_pKVPropData->deleteThis();
  195. m_pKVPropData = NULL;
  196. return;
  197. }
  198. m_bPropDataLoaded = true;
  199. // Now try and parse out the breakable section
  200. KeyValues *pBreakableSection = m_pKVPropData->FindKey( "BreakableModels" );
  201. if ( pBreakableSection )
  202. {
  203. KeyValues *pChunkSection = pBreakableSection->GetFirstSubKey();
  204. while ( pChunkSection )
  205. {
  206. // Create a new chunk section and add it to our list
  207. int index = m_BreakableChunks.AddToTail();
  208. propdata_breakablechunk_t *pBreakableChunk = &m_BreakableChunks[index];
  209. pBreakableChunk->iszChunkType = AllocPooledString( pChunkSection->GetName() );
  210. // Read in all the model names
  211. KeyValues *pModelName = pChunkSection->GetFirstSubKey();
  212. while ( pModelName )
  213. {
  214. const char *pModel = pModelName->GetName();
  215. string_t pooledName = AllocPooledString( pModel );
  216. pBreakableChunk->iszChunkModels.AddToTail( pooledName );
  217. CBaseEntity::PrecacheModel( STRING( pooledName ) );
  218. pModelName = pModelName->GetNextKey();
  219. }
  220. pChunkSection = pChunkSection->GetNextKey();
  221. }
  222. }
  223. }
  224. //-----------------------------------------------------------------------------
  225. // Purpose: Parse a keyvalues section into the prop
  226. //
  227. // pInteractionSection is a bit of jiggery-pokery to get around the unfortunate
  228. // fact that the interaction KV sections ("physgun_interactions", "fire_interactions", etc)
  229. // are OUTSIDE the "prop_data" KV section in the model, but may be contained WITHIN the
  230. // specified Base's "prop_data" section (i.e. in propdata.txt)
  231. //-----------------------------------------------------------------------------
  232. int CPropData::ParsePropFromKV( CBaseEntity *pProp, KeyValues *pSection, KeyValues *pInteractionSection )
  233. {
  234. IBreakableWithPropData *pBreakableInterface = dynamic_cast<IBreakableWithPropData*>(pProp);
  235. if ( !pBreakableInterface )
  236. return PARSE_FAILED_BAD_DATA;
  237. if ( !pBreakableInterface )
  238. return PARSE_FAILED_BAD_DATA;
  239. int iBaseResult = PARSE_SUCCEEDED;
  240. // Do we have a base?
  241. char const *pszBase = pSection->GetString( "base" );
  242. if ( pszBase && pszBase[0] )
  243. {
  244. iBaseResult = ParsePropFromBase( pProp, pszBase );
  245. if ( (iBaseResult != PARSE_SUCCEEDED) && (iBaseResult != PARSE_SUCCEEDED_ALLOWED_STATIC) )
  246. return iBaseResult;
  247. }
  248. // Allow overriding of Block LOS
  249. int iBlockLOS = pSection->GetFloat( "blockLOS", -1 );
  250. if ( iBlockLOS != -1 )
  251. {
  252. pBreakableInterface->SetPropDataBlocksLOS( iBlockLOS != 0 );
  253. }
  254. // Set whether AI can walk on this prop
  255. int iIsWalkable = pSection->GetFloat( "AIWalkable", -1 );
  256. if ( iIsWalkable != -1 )
  257. {
  258. pBreakableInterface->SetPropDataIsAIWalkable( iIsWalkable != 0 );
  259. }
  260. // Set custom damage table
  261. const char *pszTableName;
  262. if ( pBreakableInterface->GetPhysicsDamageTable() == NULL_STRING )
  263. {
  264. pszTableName = pSection->GetString( "damage_table", NULL );
  265. }
  266. else
  267. {
  268. pszTableName = pSection->GetString( "damage_table", STRING(pBreakableInterface->GetPhysicsDamageTable()) );
  269. }
  270. if ( pszTableName && pszTableName[0] )
  271. {
  272. pBreakableInterface->SetPhysicsDamageTable( AllocPooledString( pszTableName ) );
  273. }
  274. else
  275. {
  276. pBreakableInterface->SetPhysicsDamageTable( NULL_STRING );
  277. }
  278. // Get multiplayer physics mode if not set by map
  279. pBreakableInterface->SetPhysicsMode( pSection->GetInt( "physicsmode",
  280. pBreakableInterface->GetPhysicsMode() ) );
  281. const char *multiplayer_break = pSection->GetString( "multiplayer_break", NULL );
  282. if ( multiplayer_break )
  283. {
  284. mp_break_t mode = MULTIPLAYER_BREAK_DEFAULT;
  285. if ( FStrEq( multiplayer_break, "server" ) )
  286. {
  287. mode = MULTIPLAYER_BREAK_SERVERSIDE;
  288. }
  289. else if ( FStrEq( multiplayer_break, "client" ) )
  290. {
  291. mode = MULTIPLAYER_BREAK_CLIENTSIDE;
  292. }
  293. else if ( FStrEq( multiplayer_break, "both" ) )
  294. {
  295. mode = MULTIPLAYER_BREAK_BOTH;
  296. }
  297. pBreakableInterface->SetMultiplayerBreakMode( mode );
  298. }
  299. // Get damage modifiers, but only if they're specified, because our base may have already overridden them.
  300. pBreakableInterface->SetDmgModBullet( pSection->GetFloat( "dmg.bullets", pBreakableInterface->GetDmgModBullet() ) );
  301. pBreakableInterface->SetDmgModClub( pSection->GetFloat( "dmg.club", pBreakableInterface->GetDmgModClub() ) );
  302. pBreakableInterface->SetDmgModExplosive( pSection->GetFloat( "dmg.explosive", pBreakableInterface->GetDmgModExplosive() ) );
  303. // Get the health (unless this is an override prop)
  304. if ( !FClassnameIs( pProp, "prop_physics_override" ) && !FClassnameIs( pProp, "prop_dynamic_override" ) )
  305. {
  306. pProp->SetHealth( pSection->GetInt( "health", pProp->GetHealth() ) );
  307. // Explosive?
  308. pBreakableInterface->SetExplosiveDamage( pSection->GetFloat( "explosive_damage", pBreakableInterface->GetExplosiveDamage() ) );
  309. pBreakableInterface->SetExplosiveRadius( pSection->GetFloat( "explosive_radius", pBreakableInterface->GetExplosiveRadius() ) );
  310. #ifdef GAME_DLL
  311. // If we now have health, we're not allowed to ignore physics damage
  312. if ( pProp->GetHealth() )
  313. {
  314. pProp->RemoveSpawnFlags( SF_PHYSPROP_DONT_TAKE_PHYSICS_DAMAGE );
  315. }
  316. #endif
  317. }
  318. const char *pszBreakableModel;
  319. if ( pBreakableInterface->GetBreakableModel() == NULL_STRING )
  320. {
  321. pszBreakableModel = pSection->GetString( "breakable_model", NULL );
  322. }
  323. else
  324. {
  325. pszBreakableModel = pSection->GetString( "breakable_model", STRING(pBreakableInterface->GetBreakableModel()) );
  326. }
  327. if ( pszBreakableModel && pszBreakableModel[0] )
  328. {
  329. pBreakableInterface->SetBreakableModel( AllocPooledString( pszBreakableModel ) );
  330. }
  331. else
  332. {
  333. pBreakableInterface->SetBreakableModel( NULL_STRING );
  334. }
  335. pBreakableInterface->SetBreakableSkin( pSection->GetInt( "breakable_skin", pBreakableInterface->GetBreakableSkin() ) );
  336. pBreakableInterface->SetBreakableCount( pSection->GetInt( "breakable_count", pBreakableInterface->GetBreakableCount() ) );
  337. // Calculate the maximum size of the breakables this breakable will produce
  338. Vector vecSize = pProp->CollisionProp()->OBBSize();
  339. // Throw away the smallest coord
  340. int iSmallest = SmallestAxis(vecSize);
  341. vecSize[iSmallest] = 1;
  342. float flVolume = vecSize.x * vecSize.y * vecSize.z;
  343. int iMaxSize = floor( flVolume / (32.0*32.0) );
  344. pBreakableInterface->SetMaxBreakableSize( iMaxSize );
  345. // Now parse our interactions
  346. for ( int i = 0; i < PROPINTER_NUM_INTERACTIONS; i++ )
  347. {
  348. // If we hit this assert, we have too many interactions for our current storage solution to handle
  349. Assert( i < 32 );
  350. propdata_interaction_s *pInteraction = &sPropdataInteractionSections[i];
  351. KeyValues *pkvCurrentInter = pInteractionSection->FindKey( pInteraction->pszSectionName );
  352. if ( pkvCurrentInter )
  353. {
  354. char const *pszInterBase = pkvCurrentInter->GetString( pInteraction->pszKeyName );
  355. if ( pszInterBase && pszInterBase[0] && !stricmp( pszInterBase, pInteraction->pszValue ) )
  356. {
  357. pBreakableInterface->SetInteraction( (propdata_interactions_t)i );
  358. }
  359. }
  360. }
  361. // If the base said we're allowed to be static, return that
  362. if ( iBaseResult == PARSE_SUCCEEDED_ALLOWED_STATIC )
  363. return PARSE_SUCCEEDED_ALLOWED_STATIC;
  364. // Otherwise, see if our propdata says we are allowed to be static
  365. if ( pSection->GetInt( "allowstatic", 0 ) )
  366. return PARSE_SUCCEEDED_ALLOWED_STATIC;
  367. return PARSE_SUCCEEDED;
  368. }
  369. //-----------------------------------------------------------------------------
  370. // Purpose: Fill out a prop's with base data parsed from the propdata file
  371. //-----------------------------------------------------------------------------
  372. int CPropData::ParsePropFromBase( CBaseEntity *pProp, const char *pszPropData )
  373. {
  374. if ( !m_bPropDataLoaded )
  375. return PARSE_FAILED_NO_DATA;
  376. IBreakableWithPropData *pBreakableInterface = dynamic_cast<IBreakableWithPropData*>(pProp);
  377. if ( !pBreakableInterface )
  378. {
  379. return PARSE_FAILED_BAD_DATA;
  380. }
  381. if ( !m_pKVPropData )
  382. {
  383. return PARSE_FAILED_BAD_DATA;
  384. }
  385. // Find the specified propdata
  386. KeyValues *pSection = m_pKVPropData->FindKey( pszPropData );
  387. if ( !pSection )
  388. {
  389. Warning("%s '%s' has a base specified as '%s', but there is no matching entry in propdata.txt.\n", pProp->GetClassname(), STRING( pProp->GetModelName() ), pszPropData );
  390. return PARSE_FAILED_BAD_DATA;
  391. }
  392. // Store off the first base data for debugging
  393. if ( pBreakableInterface->GetBasePropData() == NULL_STRING )
  394. {
  395. pBreakableInterface->SetBasePropData( AllocPooledString( pszPropData ) );
  396. }
  397. return ParsePropFromKV( pProp, pSection, pSection );
  398. }
  399. //-----------------------------------------------------------------------------
  400. // Purpose:
  401. //-----------------------------------------------------------------------------
  402. const char *CPropData::GetRandomChunkModel( const char *pszBreakableSection, int iMaxSize )
  403. {
  404. if ( !m_bPropDataLoaded )
  405. return NULL;
  406. // Find the right section
  407. int iCount = m_BreakableChunks.Count();
  408. int i;
  409. for ( i = 0; i < iCount; i++ )
  410. {
  411. if ( !Q_strncmp( STRING(m_BreakableChunks[i].iszChunkType), pszBreakableSection, strlen(pszBreakableSection) ) )
  412. break;
  413. }
  414. if ( i == iCount )
  415. return NULL;
  416. // Now pick a random one and return it
  417. int iRandom;
  418. if ( iMaxSize == -1 )
  419. {
  420. iRandom = RandomInt( 0, m_BreakableChunks[i].iszChunkModels.Count()-1 );
  421. }
  422. else
  423. {
  424. // Don't pick anything over the specified size
  425. iRandom = RandomInt( 0, MIN(iMaxSize, m_BreakableChunks[i].iszChunkModels.Count()-1) );
  426. }
  427. return STRING(m_BreakableChunks[i].iszChunkModels[iRandom]);
  428. }
  429. // ensure that a model name from a qc file is properly formatted
  430. static const char *FixupModelName( char *pOut, int sizeOut, const char *pModelNameIn )
  431. {
  432. char tmp[1024];
  433. Q_strncpy( tmp, pModelNameIn, sizeof(tmp) );
  434. if ( Q_strnicmp( tmp, "models/", 7 ) )
  435. {
  436. Q_snprintf( pOut, sizeOut, "models/%s" , tmp );
  437. }
  438. else
  439. {
  440. Q_strncpy( pOut, tmp, sizeOut);
  441. }
  442. int len = Q_strlen(pOut);
  443. if ( len < 4 || Q_stricmp( pOut + (len-4), ".mdl" ) )
  444. {
  445. Q_strncat( pOut, ".mdl", sizeOut, COPY_ALL_CHARACTERS );
  446. }
  447. return pOut;
  448. }
  449. //-----------------------------------------------------------------------------
  450. // breakable prop functions
  451. //-----------------------------------------------------------------------------
  452. //
  453. //-----------------------------------------------------------------------------
  454. // list of models to break into
  455. class CBreakParser : public IVPhysicsKeyHandler
  456. {
  457. public:
  458. CBreakParser( float defaultBurstScale, int defaultCollisionGroup )
  459. : m_defaultBurstScale(defaultBurstScale), m_defaultCollisionGroup(defaultCollisionGroup) {}
  460. void ParseModelName( breakmodel_t *pModel, const char *pValue )
  461. {
  462. FixupModelName( pModel->modelName, sizeof(pModel->modelName), pValue );
  463. }
  464. virtual void ParseKeyValue( void *pData, const char *pKey, const char *pValue )
  465. {
  466. breakmodel_t *pModel = (breakmodel_t *)pData;
  467. if ( !strcmpi( pKey, "model" ) )
  468. {
  469. ParseModelName( pModel, pValue );
  470. }
  471. else if (!strcmpi( pKey, "ragdoll" ) )
  472. {
  473. ParseModelName( pModel, pValue );
  474. pModel->isRagdoll = true;
  475. }
  476. else if (!strcmpi( pKey, "motiondisabled" ) )
  477. {
  478. pModel->isMotionDisabled = true;
  479. }
  480. else if ( !strcmpi( pKey, "offset" ) )
  481. {
  482. UTIL_StringToVector( pModel->offset.Base(), pValue );
  483. }
  484. else if ( !strcmpi( pKey, "health" ) )
  485. {
  486. pModel->health = atof(pValue);
  487. }
  488. else if ( !strcmpi( pKey, "fadetime" ) )
  489. {
  490. pModel->fadeTime = atof(pValue);
  491. if ( !m_wroteCollisionGroup )
  492. {
  493. pModel->collisionGroup = COLLISION_GROUP_DEBRIS;
  494. }
  495. }
  496. else if ( !strcmpi( pKey, "fademindist" ) )
  497. {
  498. pModel->fadeMinDist = atof(pValue);
  499. }
  500. else if ( !strcmpi( pKey, "fademaxdist" ) )
  501. {
  502. pModel->fadeMaxDist = atof(pValue);
  503. }
  504. else if ( !strcmpi( pKey, "debris" ) )
  505. {
  506. pModel->collisionGroup = atoi(pValue) > 0 ? COLLISION_GROUP_DEBRIS : COLLISION_GROUP_INTERACTIVE;
  507. m_wroteCollisionGroup = true;
  508. }
  509. else if ( !strcmpi( pKey, "burst" ) )
  510. {
  511. pModel->burstScale = atof( pValue );
  512. }
  513. else if ( !strcmpi( pKey, "placementbone" ) )
  514. {
  515. Q_strncpy( pModel->placementName, pValue, sizeof(pModel->placementName) );
  516. pModel->placementIsBone = true;
  517. }
  518. else if ( !strcmpi( pKey, "placementattachment" ) )
  519. {
  520. Q_strncpy( pModel->placementName, pValue, sizeof(pModel->placementName) );
  521. pModel->placementIsBone = false;
  522. }
  523. else if ( !strcmpi( pKey, "multiplayer_break" ) )
  524. {
  525. if ( FStrEq( pValue, "server" ) )
  526. {
  527. pModel->mpBreakMode = MULTIPLAYER_BREAK_SERVERSIDE;
  528. }
  529. else if ( FStrEq( pValue, "client" ) )
  530. {
  531. pModel->mpBreakMode = MULTIPLAYER_BREAK_CLIENTSIDE;
  532. }
  533. }
  534. else if ( !strcmpi( pKey, "velocity" ) )
  535. {
  536. UTIL_StringToVector( pModel->velocity.Base(), pValue );
  537. }
  538. }
  539. virtual void SetDefaults( void *pData )
  540. {
  541. breakmodel_t *pModel = (breakmodel_t *)pData;
  542. pModel->modelName[0] = 0;
  543. pModel->offset = vec3_origin;
  544. pModel->health = 1;
  545. pModel->fadeTime = 20.0f;
  546. pModel->fadeMinDist = 0.0f;
  547. pModel->fadeMaxDist = 0.0f;
  548. pModel->burstScale = m_defaultBurstScale;
  549. pModel->collisionGroup = m_defaultCollisionGroup;
  550. pModel->isRagdoll = false;
  551. pModel->isMotionDisabled = false;
  552. pModel->placementName[0] = 0;
  553. pModel->placementIsBone = false;
  554. pModel->mpBreakMode = MULTIPLAYER_BREAK_DEFAULT;
  555. pModel->velocity = vec3_origin;
  556. m_wroteCollisionGroup = false;
  557. }
  558. private:
  559. int m_defaultCollisionGroup;
  560. float m_defaultBurstScale;
  561. bool m_wroteCollisionGroup;
  562. };
  563. void BuildPropList( const char *pszBlockName, CUtlVector<breakmodel_t> &list, int modelindex, float defBurstScale, int defCollisionGroup )
  564. {
  565. vcollide_t *pCollide = modelinfo->GetVCollide( modelindex );
  566. if ( !pCollide )
  567. return;
  568. IVPhysicsKeyParser *pParse = physcollision->VPhysicsKeyParserCreate( pCollide->pKeyValues );
  569. while ( !pParse->Finished() )
  570. {
  571. CBreakParser breakParser( defBurstScale, defCollisionGroup );
  572. const char *pBlock = pParse->GetCurrentBlockName();
  573. if ( !strcmpi( pBlock, pszBlockName ) )
  574. {
  575. int index = list.AddToTail();
  576. breakmodel_t &breakModel = list[index];
  577. pParse->ParseCustom( &breakModel, &breakParser );
  578. }
  579. else
  580. {
  581. pParse->SkipBlock();
  582. }
  583. }
  584. physcollision->VPhysicsKeyParserDestroy( pParse );
  585. }
  586. void BreakModelList( CUtlVector<breakmodel_t> &list, int modelindex, float defBurstScale, int defCollisionGroup )
  587. {
  588. BuildPropList( "break", list, modelindex, defBurstScale, defCollisionGroup );
  589. }
  590. #if !defined(_STATIC_LINKED) || defined(CLIENT_DLL)
  591. int GetAutoMultiplayerPhysicsMode( Vector size, float mass )
  592. {
  593. float volume = size.x * size.y * size.z;
  594. float minsize = sv_pushaway_clientside_size.GetFloat();
  595. // if it's too small, client side only
  596. if ( volume < (minsize*minsize*minsize) )
  597. return PHYSICS_MULTIPLAYER_CLIENTSIDE;
  598. // if it's too light, no player pushback
  599. if ( mass < 8.0 )
  600. return PHYSICS_MULTIPLAYER_NON_SOLID;
  601. // full pushbackmode
  602. return PHYSICS_MULTIPLAYER_SOLID;
  603. }
  604. #else
  605. extern int GetAutoMultiplayerPhysicsMode( Vector size, float mass );
  606. #endif
  607. //-----------------------------------------------------------------------------
  608. // Purpose: Returns a string describing a real-world equivalent mass.
  609. // Input : flMass - mass in kg
  610. //-----------------------------------------------------------------------------
  611. #if !defined(_STATIC_LINKED) || defined(CLIENT_DLL)
  612. const char *GetMassEquivalent(float flMass)
  613. {
  614. static struct
  615. {
  616. float flMass;
  617. const char *sz;
  618. } masstext[] =
  619. {
  620. { 5e-6, "snowflake" },
  621. { 2.5e-3, "ping-pong ball" },
  622. { 5e-3, "penny" },
  623. { 0.05, "golf ball" },
  624. { 0.17, "billard ball" },
  625. { 2, "bag of sugar" },
  626. { 7, "male cat" },
  627. { 10, "bowling ball" },
  628. { 30, "dog" },
  629. { 60, "cheetah" },
  630. { 90, "adult male human" },
  631. { 250, "refrigerator" },
  632. { 600, "race horse" },
  633. { 1000, "small car" },
  634. { 1650, "medium car" },
  635. { 2500, "large car" },
  636. { 6000, "t-rex" },
  637. { 7200, "elephant" },
  638. { 8e4, "space shuttle" },
  639. { 7e5, "locomotive" },
  640. { 9.2e6, "Eiffel tower" },
  641. { 6e24, "the Earth" },
  642. { 7e24, "really freaking heavy" },
  643. };
  644. for (int i = 0; i < sizeof(masstext) / sizeof(masstext[0]) - 1; i++)
  645. {
  646. if (flMass < masstext[i].flMass)
  647. {
  648. return masstext[i].sz;
  649. }
  650. }
  651. return masstext[ sizeof(masstext) / sizeof(masstext[0]) - 1 ].sz;
  652. }
  653. #else
  654. extern const char *GetMassEquivalent(float flMass);
  655. #endif
  656. #ifdef GAME_DLL
  657. //=========================================================
  658. //=========================================================
  659. class CGameGibManager : public CBaseEntity
  660. {
  661. DECLARE_CLASS( CGameGibManager, CBaseEntity );
  662. DECLARE_DATADESC();
  663. public:
  664. CGameGibManager() : m_iCurrentMaxPieces(-1), m_iMaxPieces(-1), m_iMaxPiecesDX8(-1) {}
  665. void Activate( void );
  666. void AddGibToLRU( CBaseAnimating *pEntity );
  667. inline bool AllowedToSpawnGib( void );
  668. private:
  669. void UpdateMaxPieces();
  670. void InputSetMaxPieces( inputdata_t &inputdata );
  671. void InputSetMaxPiecesDX8( inputdata_t &inputdata );
  672. typedef CHandle<CBaseAnimating> CGibHandle;
  673. CUtlLinkedList< CGibHandle > m_LRU;
  674. bool m_bAllowNewGibs;
  675. int m_iDXLevel;
  676. int m_iCurrentMaxPieces;
  677. int m_iMaxPieces;
  678. int m_iMaxPiecesDX8;
  679. int m_iLastFrame;
  680. };
  681. BEGIN_DATADESC( CGameGibManager )
  682. // Silence perfidous classcheck!
  683. //DEFINE_FIELD( m_iCurrentMaxPieces, FIELD_INTEGER ),
  684. //DEFINE_FIELD( m_iLastFrame, FIELD_INTEGER ),
  685. //DEFINE_FIELD( m_iDXLevel, FIELD_INTEGER ),
  686. DEFINE_KEYFIELD( m_iMaxPieces, FIELD_INTEGER, "maxpieces" ),
  687. DEFINE_KEYFIELD( m_iMaxPiecesDX8, FIELD_INTEGER, "maxpiecesdx8" ),
  688. DEFINE_KEYFIELD( m_bAllowNewGibs, FIELD_BOOLEAN, "allownewgibs" ),
  689. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMaxPieces", InputSetMaxPieces ),
  690. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMaxPiecesDX8", InputSetMaxPiecesDX8 ),
  691. END_DATADESC()
  692. LINK_ENTITY_TO_CLASS( game_gib_manager, CGameGibManager );
  693. void CGameGibManager::Activate( void )
  694. {
  695. m_LRU.Purge();
  696. // Cache off the DX level for use later.
  697. ConVarRef mat_dxlevel( "mat_dxlevel" );
  698. m_iDXLevel = mat_dxlevel.GetInt();
  699. UpdateMaxPieces();
  700. BaseClass::Activate();
  701. }
  702. void CGameGibManager::UpdateMaxPieces()
  703. {
  704. // If we're running DX8, use the DX8 gib limit if set.
  705. if ( ( m_iDXLevel < 90 ) && ( m_iMaxPiecesDX8 >= 0 ) )
  706. {
  707. m_iCurrentMaxPieces = m_iMaxPiecesDX8;
  708. }
  709. else
  710. {
  711. m_iCurrentMaxPieces = m_iMaxPieces;
  712. }
  713. }
  714. bool CGameGibManager::AllowedToSpawnGib( void )
  715. {
  716. if ( m_bAllowNewGibs )
  717. return true;
  718. // We're not tracking gibs at the moment
  719. if ( m_iCurrentMaxPieces < 0 )
  720. return true;
  721. if ( m_iCurrentMaxPieces == 0 )
  722. return false;
  723. if ( m_iLastFrame == gpGlobals->framecount )
  724. {
  725. if ( m_LRU.Count() >= m_iCurrentMaxPieces )
  726. {
  727. return false;
  728. }
  729. }
  730. return true;
  731. }
  732. void CGameGibManager::InputSetMaxPieces( inputdata_t &inputdata )
  733. {
  734. m_iMaxPieces = inputdata.value.Int();
  735. UpdateMaxPieces();
  736. }
  737. void CGameGibManager::InputSetMaxPiecesDX8( inputdata_t &inputdata )
  738. {
  739. m_iMaxPiecesDX8 = inputdata.value.Int();
  740. UpdateMaxPieces();
  741. }
  742. void CGameGibManager::AddGibToLRU( CBaseAnimating *pEntity )
  743. {
  744. int i, next;
  745. if ( pEntity == NULL )
  746. return;
  747. //Find stale gibs.
  748. for ( i = m_LRU.Head(); i < m_LRU.InvalidIndex(); i = next )
  749. {
  750. next = m_LRU.Next(i);
  751. if ( m_LRU[i].Get() == NULL )
  752. {
  753. m_LRU.Remove(i);
  754. }
  755. }
  756. // We're not tracking gibs at the moment
  757. if ( m_iCurrentMaxPieces <= 0 )
  758. return;
  759. while ( m_LRU.Count() >= m_iCurrentMaxPieces )
  760. {
  761. i = m_LRU.Head();
  762. //TODO: Make this fade out instead of pop.
  763. UTIL_Remove( m_LRU[i] );
  764. m_LRU.Remove(i);
  765. }
  766. m_LRU.AddToTail( pEntity );
  767. m_iLastFrame = gpGlobals->framecount;
  768. }
  769. EHANDLE g_hGameGibManager;
  770. CGameGibManager *GetGibManager( void )
  771. {
  772. #ifndef HL2_EPISODIC
  773. return NULL;
  774. #endif
  775. if ( g_hGameGibManager == NULL )
  776. {
  777. g_hGameGibManager = (CGameGibManager *)gEntList.FindEntityByClassname( NULL, "game_gib_manager" );
  778. }
  779. return (CGameGibManager *)g_hGameGibManager.Get();
  780. }
  781. #endif
  782. void PropBreakableCreateAll( int modelindex, IPhysicsObject *pPhysics, const breakablepropparams_t &params, CBaseEntity *pEntity, int iPrecomputedBreakableCount, bool bIgnoreGibLimit, bool defaultLocation )
  783. {
  784. // Check for prop breakable count reset.
  785. int nPropCount = props_break_max_pieces_perframe.GetInt();
  786. if ( nPropCount != -1 )
  787. {
  788. if ( nFrameNumber != gpGlobals->framecount )
  789. {
  790. nPropBreakablesPerFrameCount = 0;
  791. nFrameNumber = gpGlobals->framecount;
  792. }
  793. // Check for max breakable count for the frame.
  794. if ( nPropBreakablesPerFrameCount >= nPropCount )
  795. return;
  796. }
  797. int iMaxBreakCount = bIgnoreGibLimit ? -1 : props_break_max_pieces.GetInt();
  798. if ( iMaxBreakCount != -1 )
  799. {
  800. if ( iPrecomputedBreakableCount != -1 )
  801. {
  802. iPrecomputedBreakableCount = MIN( iMaxBreakCount, iPrecomputedBreakableCount );
  803. }
  804. else
  805. {
  806. iPrecomputedBreakableCount = iMaxBreakCount;
  807. }
  808. }
  809. #ifdef GAME_DLL
  810. // On server limit break model creation
  811. if ( !PropBreakableCapEdictsOnCreateAll(modelindex, pPhysics, params, pEntity, iPrecomputedBreakableCount ) )
  812. {
  813. DevMsg( "Failed to create PropBreakable: would exceed MAX_EDICTS\n" );
  814. return;
  815. }
  816. #endif
  817. vcollide_t *pCollide = modelinfo->GetVCollide( modelindex );
  818. if ( !pCollide )
  819. return;
  820. int nSkin = 0;
  821. CBaseEntity *pOwnerEntity = pEntity;
  822. CBaseAnimating *pOwnerAnim = NULL;
  823. if ( pPhysics )
  824. {
  825. pOwnerEntity = static_cast<CBaseEntity *>(pPhysics->GetGameData());
  826. }
  827. if ( pOwnerEntity )
  828. {
  829. pOwnerAnim = pOwnerEntity->GetBaseAnimating();
  830. if ( pOwnerAnim )
  831. {
  832. nSkin = pOwnerAnim->m_nSkin;
  833. }
  834. }
  835. matrix3x4_t localToWorld;
  836. CStudioHdr studioHdr;
  837. const model_t *model = modelinfo->GetModel( modelindex );
  838. if ( model )
  839. {
  840. studioHdr.Init( modelinfo->GetStudiomodel( model ) );
  841. }
  842. Vector parentOrigin = vec3_origin;
  843. int parentAttachment = Studio_FindAttachment( &studioHdr, "placementOrigin" ) + 1;
  844. if ( parentAttachment > 0 )
  845. {
  846. GetAttachmentLocalSpace( &studioHdr, parentAttachment-1, localToWorld );
  847. MatrixGetColumn( localToWorld, 3, parentOrigin );
  848. }
  849. else
  850. {
  851. AngleMatrix( vec3_angle, localToWorld );
  852. }
  853. CUtlVector<breakmodel_t> list;
  854. BreakModelList( list, modelindex, params.defBurstScale, params.defCollisionGroup );
  855. if ( list.Count() )
  856. {
  857. for ( int i = 0; i < list.Count(); i++ )
  858. {
  859. int modelIndex = modelinfo->GetModelIndex( list[i].modelName );
  860. if ( modelIndex <= 0 )
  861. continue;
  862. // Skip multiplayer pieces that should be spawning on the other dll
  863. #ifdef GAME_DLL
  864. if ( gpGlobals->maxClients > 1 && breakable_multiplayer.GetBool() )
  865. #else
  866. if ( gpGlobals->maxClients > 1 )
  867. #endif
  868. {
  869. #ifdef GAME_DLL
  870. if ( list[i].mpBreakMode == MULTIPLAYER_BREAK_CLIENTSIDE )
  871. continue;
  872. #else
  873. if ( list[i].mpBreakMode == MULTIPLAYER_BREAK_SERVERSIDE )
  874. continue;
  875. #endif
  876. if ( !defaultLocation && list[i].mpBreakMode == MULTIPLAYER_BREAK_DEFAULT )
  877. continue;
  878. }
  879. if ( ( nPropCount != -1 ) && ( nPropBreakablesPerFrameCount > nPropCount ) )
  880. break;
  881. if ( ( iPrecomputedBreakableCount != -1 ) && ( i >= iPrecomputedBreakableCount ) )
  882. break;
  883. matrix3x4_t matrix;
  884. AngleMatrix( params.angles, params.origin, matrix );
  885. CStudioHdr studioHdrModel;
  886. const model_t *pModel = modelinfo->GetModel( modelIndex );
  887. if ( pModel )
  888. {
  889. studioHdrModel.Init( modelinfo->GetStudiomodel( pModel ) );
  890. }
  891. // Increment the number of breakable props this frame.
  892. ++nPropBreakablesPerFrameCount;
  893. Vector position = vec3_origin;
  894. QAngle angles = params.angles;
  895. if ( pOwnerAnim && list[i].placementName[0] )
  896. {
  897. if ( list[i].placementIsBone )
  898. {
  899. int boneIndex = pOwnerAnim->LookupBone( list[i].placementName );
  900. if ( boneIndex >= 0 )
  901. {
  902. pOwnerAnim->GetBonePosition( boneIndex, position, angles );
  903. AngleMatrix( angles, position, matrix );
  904. }
  905. }
  906. else
  907. {
  908. int attachmentIndex = Studio_FindAttachment( &studioHdrModel, list[i].placementName ) + 1;
  909. if ( attachmentIndex > 0 )
  910. {
  911. pOwnerAnim->GetAttachment( attachmentIndex, matrix );
  912. MatrixAngles( matrix, angles );
  913. }
  914. }
  915. }
  916. else
  917. {
  918. int placementIndex = Studio_FindAttachment( &studioHdrModel, "placementOrigin" ) + 1;
  919. Vector placementOrigin = parentOrigin;
  920. if ( placementIndex > 0 )
  921. {
  922. GetAttachmentLocalSpace( &studioHdrModel, placementIndex-1, localToWorld );
  923. MatrixGetColumn( localToWorld, 3, placementOrigin );
  924. placementOrigin -= parentOrigin;
  925. }
  926. VectorTransform( list[i].offset - placementOrigin, matrix, position );
  927. }
  928. Vector objectVelocity = params.velocity;
  929. if (pPhysics)
  930. {
  931. pPhysics->GetVelocityAtPoint( position, &objectVelocity );
  932. }
  933. int nActualSkin = nSkin;
  934. if ( nActualSkin > studioHdrModel.numskinfamilies() )
  935. nActualSkin = 0;
  936. CBaseEntity *pBreakable = NULL;
  937. #ifdef GAME_DLL
  938. if ( GetGibManager() == NULL || GetGibManager()->AllowedToSpawnGib() )
  939. #endif
  940. {
  941. pBreakable = BreakModelCreateSingle( pOwnerEntity, &list[i], position, angles, objectVelocity, params.angularVelocity, nActualSkin, params );
  942. }
  943. if ( pBreakable )
  944. {
  945. #ifdef GAME_DLL
  946. if ( GetGibManager() )
  947. {
  948. GetGibManager()->AddGibToLRU( pBreakable->GetBaseAnimating() );
  949. }
  950. #endif
  951. if ( pOwnerEntity && pOwnerEntity->IsEffectActive( EF_NOSHADOW ) )
  952. {
  953. pBreakable->AddEffects( EF_NOSHADOW );
  954. }
  955. // If burst scale is set, this piece should 'burst' away from
  956. // the origin in addition to travelling in the wished velocity.
  957. if ( list[i].burstScale != 0.0 )
  958. {
  959. Vector vecBurstDir = position - params.origin;
  960. // If $autocenter wasn't used, try the center of the piece
  961. if ( vecBurstDir == vec3_origin )
  962. {
  963. vecBurstDir = pBreakable->WorldSpaceCenter() - params.origin;
  964. }
  965. VectorNormalize( vecBurstDir );
  966. pBreakable->ApplyAbsVelocityImpulse( vecBurstDir * list[i].burstScale );
  967. }
  968. // If this piece is supposed to be motion disabled, disable it
  969. if ( list[i].isMotionDisabled )
  970. {
  971. IPhysicsObject *pPhysicsObject = pBreakable->VPhysicsGetObject();
  972. if ( pPhysicsObject != NULL )
  973. {
  974. pPhysicsObject->EnableMotion( false );
  975. }
  976. }
  977. }
  978. }
  979. }
  980. // Then see if the propdata specifies any breakable pieces
  981. else if ( pEntity )
  982. {
  983. IBreakableWithPropData *pBreakableInterface = dynamic_cast<IBreakableWithPropData*>(pEntity);
  984. if ( pBreakableInterface && pBreakableInterface->GetBreakableModel() != NULL_STRING && pBreakableInterface->GetBreakableCount() )
  985. {
  986. breakmodel_t breakModel;
  987. for ( int i = 0; i < pBreakableInterface->GetBreakableCount(); i++ )
  988. {
  989. if ( ( iPrecomputedBreakableCount != -1 ) && ( i >= iPrecomputedBreakableCount ) )
  990. break;
  991. Q_strncpy( breakModel.modelName, g_PropDataSystem.GetRandomChunkModel(STRING(pBreakableInterface->GetBreakableModel()), pBreakableInterface->GetMaxBreakableSize()), sizeof(breakModel.modelName) );
  992. breakModel.health = 1;
  993. breakModel.fadeTime = RandomFloat(5,10);
  994. breakModel.fadeMinDist = 0.0f;
  995. breakModel.fadeMaxDist = 0.0f;
  996. breakModel.burstScale = params.defBurstScale;
  997. breakModel.collisionGroup = COLLISION_GROUP_DEBRIS;
  998. breakModel.isRagdoll = false;
  999. breakModel.isMotionDisabled = false;
  1000. breakModel.placementName[0] = 0;
  1001. breakModel.placementIsBone = false;
  1002. Vector vecObbSize = pEntity->CollisionProp()->OBBSize();
  1003. // Find a random point on the plane of the original's two largest axis
  1004. int smallestAxis = SmallestAxis( vecObbSize );
  1005. Vector vecMins(0,0,0);
  1006. Vector vecMaxs(1,1,1);
  1007. vecMins[smallestAxis] = 0.5;
  1008. vecMaxs[smallestAxis] = 0.5;
  1009. pEntity->CollisionProp()->RandomPointInBounds( vecMins, vecMaxs, &breakModel.offset );
  1010. // Push all chunks away from the center
  1011. Vector vecBurstDir = breakModel.offset - params.origin;
  1012. VectorNormalize( vecBurstDir );
  1013. Vector vecVelocity = vecBurstDir * params.defBurstScale;
  1014. QAngle vecAngles = pEntity->GetAbsAngles();
  1015. int iSkin = pBreakableInterface->GetBreakableSkin();
  1016. CBaseEntity *pBreakable = NULL;
  1017. #ifdef GAME_DLL
  1018. if ( GetGibManager() == NULL || GetGibManager()->AllowedToSpawnGib() )
  1019. #endif
  1020. {
  1021. pBreakable = BreakModelCreateSingle( pOwnerEntity, &breakModel, breakModel.offset, vecAngles, vecVelocity, vec3_origin/*params.angularVelocity*/, iSkin, params );
  1022. if ( !pBreakable )
  1023. {
  1024. DevWarning( "PropBreakableCreateAll: Could not create model %s\n", breakModel.modelName );
  1025. }
  1026. }
  1027. if ( pBreakable )
  1028. {
  1029. #ifdef GAME_DLL
  1030. if ( GetGibManager() )
  1031. {
  1032. GetGibManager()->AddGibToLRU( pBreakable->GetBaseAnimating() );
  1033. }
  1034. #endif
  1035. Vector vecBreakableObbSize = pBreakable->CollisionProp()->OBBSize();
  1036. // Try to align the gibs along the original axis
  1037. matrix3x4_t matrix;
  1038. AngleMatrix( vecAngles, matrix );
  1039. AlignBoxes( &matrix, vecObbSize, vecBreakableObbSize );
  1040. MatrixAngles( matrix, vecAngles );
  1041. if ( pBreakable->VPhysicsGetObject() )
  1042. {
  1043. Vector pos;
  1044. pBreakable->VPhysicsGetObject()->GetPosition( &pos, NULL );
  1045. pBreakable->VPhysicsGetObject()->SetPosition( pos, vecAngles, true );
  1046. }
  1047. pBreakable->SetAbsAngles( vecAngles );
  1048. if ( pOwnerEntity->IsEffectActive( EF_NOSHADOW ) )
  1049. {
  1050. pBreakable->AddEffects( EF_NOSHADOW );
  1051. }
  1052. }
  1053. }
  1054. }
  1055. }
  1056. }
  1057. void PropBreakableCreateAll( int modelindex, IPhysicsObject *pPhysics, const Vector &origin, const QAngle &angles, const Vector &velocity, const AngularImpulse &angularVelocity, float impactEnergyScale, float defBurstScale, int defCollisionGroup, CBaseEntity *pEntity, bool defaultLocation )
  1058. {
  1059. breakablepropparams_t params( origin, angles, velocity, angularVelocity );
  1060. params.impactEnergyScale = impactEnergyScale;
  1061. params.defBurstScale = defBurstScale;
  1062. params.defCollisionGroup = defCollisionGroup;
  1063. PropBreakableCreateAll( modelindex, pPhysics, params, pEntity, -1, false, defaultLocation );
  1064. }
  1065. //-----------------------------------------------------------------------------
  1066. // Purpose:
  1067. // Input : modelindex -
  1068. //-----------------------------------------------------------------------------
  1069. void PrecachePropsForModel( int iModel, const char *pszBlockName )
  1070. {
  1071. vcollide_t *pCollide = modelinfo->GetVCollide( iModel );
  1072. if ( !pCollide )
  1073. return;
  1074. // The scale and group doesn't really matter at the moment, we are just using the parser to get the model name to cache.
  1075. CBreakParser breakParser( 1.0, COLLISION_GROUP_NONE );
  1076. // Create a parser.
  1077. IVPhysicsKeyParser *pParse = physcollision->VPhysicsKeyParserCreate( pCollide->pKeyValues );
  1078. while ( !pParse->Finished() )
  1079. {
  1080. const char *pBlock = pParse->GetCurrentBlockName();
  1081. if ( !strcmpi( pBlock, pszBlockName ) )
  1082. {
  1083. breakmodel_t breakModel;
  1084. pParse->ParseCustom( &breakModel, &breakParser );
  1085. CBaseEntity::PrecacheModel( breakModel.modelName );
  1086. }
  1087. else
  1088. {
  1089. pParse->SkipBlock();
  1090. }
  1091. }
  1092. // Destroy the parser.
  1093. physcollision->VPhysicsKeyParserDestroy( pParse );
  1094. }
  1095. void PrecacheGibsForModel( int iModel )
  1096. {
  1097. VPROF_BUDGET( "PrecacheGibsForModel", VPROF_BUDGETGROUP_PLAYER );
  1098. PrecachePropsForModel( iModel, "break" );
  1099. }
  1100. //-----------------------------------------------------------------------------
  1101. // Purpose:
  1102. // Input : &list -
  1103. // modelindex -
  1104. // defBurstScale -
  1105. // defCollisionGroup -
  1106. //-----------------------------------------------------------------------------
  1107. void BuildGibList( CUtlVector<breakmodel_t> &list, int modelindex, float defBurstScale, int defCollisionGroup )
  1108. {
  1109. BreakModelList( list, modelindex, defBurstScale, defCollisionGroup );
  1110. }
  1111. //-----------------------------------------------------------------------------
  1112. // Purpose:
  1113. // Input : &list -
  1114. // modelindex -
  1115. // *pPhysics -
  1116. // &params -
  1117. // *pEntity -
  1118. // iPrecomputedBreakableCount -
  1119. // bIgnoreGibLImit -
  1120. // defaultLocation -
  1121. //-----------------------------------------------------------------------------
  1122. CBaseEntity *CreateGibsFromList( CUtlVector<breakmodel_t> &list, int modelindex, IPhysicsObject *pPhysics, const breakablepropparams_t &params, CBaseEntity *pEntity, int iPrecomputedBreakableCount, bool bIgnoreGibLimit, bool defaultLocation, CUtlVector<EHANDLE> *pGibList, bool bBurning )
  1123. {
  1124. // Check for prop breakable count reset.
  1125. int nPropCount = props_break_max_pieces_perframe.GetInt();
  1126. if ( nPropCount != -1 )
  1127. {
  1128. if ( nFrameNumber != gpGlobals->framecount )
  1129. {
  1130. nPropBreakablesPerFrameCount = 0;
  1131. nFrameNumber = gpGlobals->framecount;
  1132. }
  1133. // Check for max breakable count for the frame.
  1134. if ( nPropBreakablesPerFrameCount >= nPropCount )
  1135. return NULL;
  1136. }
  1137. int iMaxBreakCount = bIgnoreGibLimit ? -1 : props_break_max_pieces.GetInt();
  1138. if ( iMaxBreakCount != -1 )
  1139. {
  1140. if ( iPrecomputedBreakableCount != -1 )
  1141. {
  1142. iPrecomputedBreakableCount = MIN( iMaxBreakCount, iPrecomputedBreakableCount );
  1143. }
  1144. else
  1145. {
  1146. iPrecomputedBreakableCount = iMaxBreakCount;
  1147. }
  1148. }
  1149. #ifdef GAME_DLL
  1150. // On server limit break model creation
  1151. if ( !PropBreakableCapEdictsOnCreateAll(modelindex, pPhysics, params, pEntity, iPrecomputedBreakableCount ) )
  1152. {
  1153. DevMsg( "Failed to create PropBreakable: would exceed MAX_EDICTS\n" );
  1154. return NULL;
  1155. }
  1156. #endif
  1157. vcollide_t *pCollide = modelinfo->GetVCollide( modelindex );
  1158. if ( !pCollide )
  1159. return NULL;
  1160. int nSkin = params.nDefaultSkin;
  1161. CBaseEntity *pOwnerEntity = pEntity;
  1162. CBaseAnimating *pOwnerAnim = NULL;
  1163. if ( pPhysics )
  1164. {
  1165. pOwnerEntity = static_cast<CBaseEntity *>(pPhysics->GetGameData());
  1166. }
  1167. if ( pOwnerEntity )
  1168. {
  1169. pOwnerAnim = dynamic_cast<CBaseAnimating*>(pOwnerEntity);
  1170. if ( pOwnerAnim )
  1171. {
  1172. nSkin = pOwnerAnim->m_nSkin;
  1173. }
  1174. }
  1175. matrix3x4_t localToWorld;
  1176. CStudioHdr studioHdrParent;
  1177. const model_t *model = modelinfo->GetModel( modelindex );
  1178. if ( model )
  1179. {
  1180. studioHdrParent.Init( modelinfo->GetStudiomodel( model ) );
  1181. }
  1182. Vector parentOrigin = vec3_origin;
  1183. int parentAttachment = Studio_FindAttachment( &studioHdrParent, "placementOrigin" ) + 1;
  1184. if ( parentAttachment > 0 )
  1185. {
  1186. GetAttachmentLocalSpace( &studioHdrParent, parentAttachment-1, localToWorld );
  1187. MatrixGetColumn( localToWorld, 3, parentOrigin );
  1188. }
  1189. else
  1190. {
  1191. AngleMatrix( vec3_angle, localToWorld );
  1192. }
  1193. // CUtlVector<breakmodel_t> list;
  1194. // BreakModelList( list, modelindex, params.defBurstScale, params.defCollisionGroup );
  1195. CBaseEntity *pFirstBreakable = NULL;
  1196. if ( list.Count() )
  1197. {
  1198. for ( int i = 0; i < list.Count(); i++ )
  1199. {
  1200. int modelIndex = modelinfo->GetModelIndex( list[i].modelName );
  1201. if ( modelIndex <= 0 )
  1202. continue;
  1203. // Skip multiplayer pieces that should be spawning on the other dll
  1204. #ifdef GAME_DLL
  1205. if ( gpGlobals->maxClients > 1 && breakable_multiplayer.GetBool() )
  1206. #else
  1207. if ( gpGlobals->maxClients > 1 )
  1208. #endif
  1209. {
  1210. #ifdef GAME_DLL
  1211. if ( list[i].mpBreakMode == MULTIPLAYER_BREAK_CLIENTSIDE )
  1212. continue;
  1213. #else
  1214. if ( list[i].mpBreakMode == MULTIPLAYER_BREAK_SERVERSIDE )
  1215. continue;
  1216. #endif
  1217. if ( !defaultLocation && list[i].mpBreakMode == MULTIPLAYER_BREAK_DEFAULT )
  1218. continue;
  1219. }
  1220. if ( ( nPropCount != -1 ) && ( nPropBreakablesPerFrameCount > nPropCount ) )
  1221. break;
  1222. if ( ( iPrecomputedBreakableCount != -1 ) && ( i >= iPrecomputedBreakableCount ) )
  1223. break;
  1224. matrix3x4_t matrix;
  1225. AngleMatrix( params.angles, params.origin, matrix );
  1226. CStudioHdr studioHdrModel;
  1227. const model_t *pModel = modelinfo->GetModel( modelIndex );
  1228. if ( pModel )
  1229. {
  1230. studioHdrModel.Init( modelinfo->GetStudiomodel( pModel ) );
  1231. }
  1232. // Increment the number of breakable props this frame.
  1233. ++nPropBreakablesPerFrameCount;
  1234. Vector position = vec3_origin;
  1235. QAngle angles = params.angles;
  1236. if ( pOwnerAnim && list[i].placementName[0] )
  1237. {
  1238. if ( list[i].placementIsBone )
  1239. {
  1240. int boneIndex = pOwnerAnim->LookupBone( list[i].placementName );
  1241. if ( boneIndex >= 0 )
  1242. {
  1243. pOwnerAnim->GetBonePosition( boneIndex, position, angles );
  1244. AngleMatrix( angles, position, matrix );
  1245. }
  1246. }
  1247. else
  1248. {
  1249. int attachmentIndex = Studio_FindAttachment( &studioHdrModel, list[i].placementName ) + 1;
  1250. if ( attachmentIndex > 0 )
  1251. {
  1252. pOwnerAnim->GetAttachment( attachmentIndex, matrix );
  1253. MatrixAngles( matrix, angles );
  1254. }
  1255. }
  1256. }
  1257. else
  1258. {
  1259. int placementIndex = Studio_FindAttachment( &studioHdrModel, "placementOrigin" ) + 1;
  1260. Vector placementOrigin = parentOrigin;
  1261. if ( placementIndex > 0 )
  1262. {
  1263. GetAttachmentLocalSpace( &studioHdrModel, placementIndex-1, localToWorld );
  1264. MatrixGetColumn( localToWorld, 3, placementOrigin );
  1265. placementOrigin -= parentOrigin;
  1266. }
  1267. VectorTransform( list[i].offset - placementOrigin, matrix, position );
  1268. }
  1269. Vector objectVelocity = params.velocity;
  1270. Vector gibVelocity = vec3_origin;
  1271. if ( !list[i].velocity.IsZero() )
  1272. {
  1273. VectorRotate( list[i].velocity, matrix, gibVelocity );
  1274. objectVelocity = gibVelocity;
  1275. }
  1276. else
  1277. {
  1278. float flScale = VectorNormalize( objectVelocity );
  1279. objectVelocity.x += RandomFloat( -1.f, 1.0f );
  1280. objectVelocity.y += RandomFloat( -1.0f, 1.0f );
  1281. objectVelocity.z += RandomFloat( 0.0f, 1.0f );
  1282. VectorNormalize( objectVelocity );
  1283. objectVelocity *= flScale;
  1284. }
  1285. if (pPhysics)
  1286. {
  1287. pPhysics->GetVelocityAtPoint( position, &objectVelocity );
  1288. }
  1289. int nActualSkin = nSkin;
  1290. if ( nActualSkin > studioHdrModel.numskinfamilies() )
  1291. nActualSkin = 0;
  1292. CBaseEntity *pBreakable = NULL;
  1293. #ifdef GAME_DLL
  1294. if ( GetGibManager() == NULL || GetGibManager()->AllowedToSpawnGib() )
  1295. #endif
  1296. {
  1297. pBreakable = BreakModelCreateSingle( pOwnerEntity, &list[i], position, angles, objectVelocity, params.angularVelocity, nActualSkin, params );
  1298. }
  1299. if ( pBreakable )
  1300. {
  1301. #ifdef GAME_DLL
  1302. if ( GetGibManager() )
  1303. {
  1304. GetGibManager()->AddGibToLRU( pBreakable->GetBaseAnimating() );
  1305. }
  1306. #endif
  1307. #ifndef GAME_DLL
  1308. if ( bBurning && cl_burninggibs.GetBool() )
  1309. {
  1310. pBreakable->ParticleProp()->Create( "burninggibs", PATTACH_POINT_FOLLOW, "bloodpoint" );
  1311. }
  1312. #endif
  1313. if ( pOwnerEntity && pOwnerEntity->IsEffectActive( EF_NOSHADOW ) )
  1314. {
  1315. pBreakable->AddEffects( EF_NOSHADOW );
  1316. }
  1317. // If burst scale is set, this piece should 'burst' away from
  1318. // the origin in addition to travelling in the wished velocity.
  1319. if ( list[i].burstScale != 0.0 )
  1320. {
  1321. Vector vecBurstDir = position - params.origin;
  1322. // If $autocenter wasn't used, try the center of the piece
  1323. if ( vecBurstDir == vec3_origin )
  1324. {
  1325. vecBurstDir = pBreakable->WorldSpaceCenter() - params.origin;
  1326. }
  1327. VectorNormalize( vecBurstDir );
  1328. pBreakable->ApplyAbsVelocityImpulse( vecBurstDir * list[i].burstScale );
  1329. }
  1330. // If this piece is supposed to be motion disabled, disable it
  1331. if ( list[i].isMotionDisabled )
  1332. {
  1333. IPhysicsObject *pPhysicsObject = pBreakable->VPhysicsGetObject();
  1334. if ( pPhysicsObject != NULL )
  1335. {
  1336. pPhysicsObject->EnableMotion( false );
  1337. }
  1338. }
  1339. if ( !pFirstBreakable )
  1340. {
  1341. pFirstBreakable = pBreakable;
  1342. }
  1343. if ( pGibList )
  1344. {
  1345. pGibList->AddToTail( pBreakable );
  1346. }
  1347. }
  1348. }
  1349. }
  1350. // Then see if the propdata specifies any breakable pieces
  1351. else if ( pEntity )
  1352. {
  1353. IBreakableWithPropData *pBreakableInterface = dynamic_cast<IBreakableWithPropData*>(pEntity);
  1354. if ( pBreakableInterface && pBreakableInterface->GetBreakableModel() != NULL_STRING && pBreakableInterface->GetBreakableCount() )
  1355. {
  1356. breakmodel_t breakModel;
  1357. for ( int i = 0; i < pBreakableInterface->GetBreakableCount(); i++ )
  1358. {
  1359. if ( ( iPrecomputedBreakableCount != -1 ) && ( i >= iPrecomputedBreakableCount ) )
  1360. break;
  1361. Q_strncpy( breakModel.modelName, g_PropDataSystem.GetRandomChunkModel(STRING(pBreakableInterface->GetBreakableModel()), pBreakableInterface->GetMaxBreakableSize()), sizeof(breakModel.modelName) );
  1362. breakModel.health = 1;
  1363. breakModel.fadeTime = RandomFloat(5,10);
  1364. breakModel.fadeMinDist = 0.0f;
  1365. breakModel.fadeMaxDist = 0.0f;
  1366. breakModel.burstScale = params.defBurstScale;
  1367. breakModel.collisionGroup = COLLISION_GROUP_DEBRIS;
  1368. breakModel.isRagdoll = false;
  1369. breakModel.isMotionDisabled = false;
  1370. breakModel.placementName[0] = 0;
  1371. breakModel.placementIsBone = false;
  1372. Vector vecObbSize = pEntity->CollisionProp()->OBBSize();
  1373. // Find a random point on the plane of the original's two largest axis
  1374. int smallestAxis = SmallestAxis( vecObbSize );
  1375. Vector vecMins(0,0,0);
  1376. Vector vecMaxs(1,1,1);
  1377. vecMins[smallestAxis] = 0.5;
  1378. vecMaxs[smallestAxis] = 0.5;
  1379. pEntity->CollisionProp()->RandomPointInBounds( vecMins, vecMaxs, &breakModel.offset );
  1380. // Push all chunks away from the center
  1381. Vector vecBurstDir = breakModel.offset - params.origin;
  1382. VectorNormalize( vecBurstDir );
  1383. Vector vecVelocity = vecBurstDir * params.defBurstScale;
  1384. QAngle vecAngles = pEntity->GetAbsAngles();
  1385. int iSkin = pBreakableInterface->GetBreakableSkin();
  1386. CBaseEntity *pBreakable = NULL;
  1387. #ifdef GAME_DLL
  1388. if ( GetGibManager() == NULL || GetGibManager()->AllowedToSpawnGib() )
  1389. #endif
  1390. {
  1391. pBreakable = BreakModelCreateSingle( pOwnerEntity, &breakModel, breakModel.offset, vecAngles, vecVelocity, vec3_origin/*params.angularVelocity*/, iSkin, params );
  1392. }
  1393. if( pBreakable )
  1394. {
  1395. #ifdef GAME_DLL
  1396. if ( GetGibManager() )
  1397. {
  1398. GetGibManager()->AddGibToLRU( pBreakable->GetBaseAnimating() );
  1399. }
  1400. #endif
  1401. Vector vecBreakableObbSize = pBreakable->CollisionProp()->OBBSize();
  1402. // Try to align the gibs along the original axis
  1403. matrix3x4_t matrix;
  1404. AngleMatrix( vecAngles, matrix );
  1405. AlignBoxes( &matrix, vecObbSize, vecBreakableObbSize );
  1406. MatrixAngles( matrix, vecAngles );
  1407. if ( pBreakable->VPhysicsGetObject() )
  1408. {
  1409. Vector pos;
  1410. pBreakable->VPhysicsGetObject()->GetPosition( &pos, NULL );
  1411. pBreakable->VPhysicsGetObject()->SetPosition( pos, vecAngles, true );
  1412. }
  1413. pBreakable->SetAbsAngles( vecAngles );
  1414. if ( pOwnerEntity->IsEffectActive( EF_NOSHADOW ) )
  1415. {
  1416. pBreakable->AddEffects( EF_NOSHADOW );
  1417. }
  1418. if ( !pFirstBreakable )
  1419. {
  1420. pFirstBreakable = pBreakable;
  1421. }
  1422. if ( pGibList )
  1423. {
  1424. pGibList->AddToTail( pBreakable );
  1425. }
  1426. }
  1427. else
  1428. {
  1429. DevWarning( "PropBreakableCreateAll: Could not create model %s\n", breakModel.modelName );
  1430. }
  1431. }
  1432. }
  1433. }
  1434. return pFirstBreakable;
  1435. }