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.

1755 lines
53 KiB

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