aboutsummaryrefslogtreecommitdiff
path: root/sp/src/game/shared/props_shared.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /sp/src/game/shared/props_shared.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'sp/src/game/shared/props_shared.cpp')
-rw-r--r--sp/src/game/shared/props_shared.cpp3276
1 files changed, 1638 insertions, 1638 deletions
diff --git a/sp/src/game/shared/props_shared.cpp b/sp/src/game/shared/props_shared.cpp
index 86a8e865..7cf4489b 100644
--- a/sp/src/game/shared/props_shared.cpp
+++ b/sp/src/game/shared/props_shared.cpp
@@ -1,1638 +1,1638 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose: static_prop - don't move, don't animate, don't do anything.
-// physics_prop - move, take damage, but don't animate
-//
-//=============================================================================//
-
-
-#include "cbase.h"
-#include "props_shared.h"
-#include "filesystem.h"
-#include "animation.h"
-#include <vcollide_parse.h>
-#include <bone_setup.h>
-#include <tier0/vprof.h>
-
-#ifdef CLIENT_DLL
-#include "gamestringpool.h"
-#endif
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-ConVar sv_pushaway_clientside_size( "sv_pushaway_clientside_size", "15", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Minimum size of pushback objects" );
-ConVar props_break_max_pieces( "props_break_max_pieces", "-1", 0, "Maximum prop breakable piece count (-1 = model default)" );
-ConVar props_break_max_pieces_perframe( "props_break_max_pieces_perframe", "-1", FCVAR_REPLICATED, "Maximum prop breakable piece count per frame (-1 = model default)" );
-#ifdef GAME_DLL
-extern ConVar breakable_multiplayer;
-#else
-ConVar cl_burninggibs( "cl_burninggibs", "0", 0, "A burning player that gibs has burning gibs." );
-#endif // GAME_DLL
-
-extern bool PropBreakableCapEdictsOnCreateAll(int modelindex, IPhysicsObject *pPhysics, const breakablepropparams_t &params, CBaseEntity *pEntity, int iPrecomputedBreakableCount = -1 );
-extern CBaseEntity *BreakModelCreateSingle( CBaseEntity *pOwner, breakmodel_t *pModel, const Vector &position,
- const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, int nSkin, const breakablepropparams_t &params );
-
-static int nPropBreakablesPerFrameCount = 0;
-static int nFrameNumber = 0;
-
-//=============================================================================================================
-// UTILITY FUNCS
-//=============================================================================================================
-//-----------------------------------------------------------------------------
-// Purpose: returns the axis index with the greatest size
-// Input : &vec -
-// Output : static int
-//-----------------------------------------------------------------------------
-static int GreatestAxis( const Vector &vec )
-{
- if ( vec.x > vec.y )
- {
- if ( vec.x > vec.z )
- return 0;
- return 2;
- }
- if ( vec.y > vec.z )
- return 1;
- return 2;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: returns the axis index with the smallest size
-// Input : &vec -
-// Output : static int
-//-----------------------------------------------------------------------------
-static int SmallestAxis( const Vector &vec )
-{
- if ( vec.x < vec.y )
- {
- if ( vec.x < vec.z )
- return 0;
- return 2;
- }
- if ( vec.y < vec.z )
- return 1;
- return 2;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Rotates a matrix by 90 degrees in the plane of axis0/axis1
-// Input : &matrix -
-// axis0 -
-// axis1 -
-// Output : static void
-//-----------------------------------------------------------------------------
-static void MatrixRot90( matrix3x4_t &matrix, int axis0, int axis1 )
-{
- Vector col0, col1;
- MatrixGetColumn( matrix, axis0, col0 );
- MatrixGetColumn( matrix, axis1, col1 );
- MatrixSetColumn( col1, axis0, matrix );
- MatrixSetColumn( -col0, axis1, matrix );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Given two symmetric boxes, rotate the coordinate frame by the necessary
-// 90 degree rotations to approximately align them
-// Input : *pInOutMatrix -
-// &boxExtents1 -
-// &boxExtents2 -
-//-----------------------------------------------------------------------------
-static void AlignBoxes( matrix3x4_t *pInOutMatrix, const Vector &boxExtents1, const Vector &boxExtents2 )
-{
- int rotCount = 0;
- struct
- {
- int axis0;
- int axis1;
- } rotations[2];
- Vector ext1 = boxExtents1;
- Vector ext2 = boxExtents2;
-
- int axis0 = GreatestAxis( ext1 );
- int axis1 = GreatestAxis( ext2 );
- if ( axis0 != axis1 )
- {
- rotations[rotCount].axis0 = axis0;
- rotations[rotCount].axis1 = axis1;
- rotCount++;
- ext2[axis1] = ext2[axis0];
- }
- ext1[axis0] = 0;
- ext2[axis0] = 0;
-
- axis0 = GreatestAxis(ext1);
- axis1 = GreatestAxis(ext2);
- if ( axis0 != axis1 )
- {
- rotations[rotCount].axis0 = axis0;
- rotations[rotCount].axis1 = axis1;
- rotCount++;
- }
-
- while ( rotCount > 0 )
- {
- rotCount--;
- MatrixRot90( *pInOutMatrix, rotations[rotCount].axis0, rotations[rotCount].axis1 );
- }
-}
-
-//=============================================================================================================
-// PROP DATA
-//=============================================================================================================
-CPropData g_PropDataSystem;
-
-// Parsing details for each of the propdata interactions
-struct propdata_interaction_s
-{
- const char *pszSectionName;
- const char *pszKeyName;
- const char *pszValue;
-};
-
-#if !defined(_STATIC_LINKED) || defined(CLIENT_DLL)
-propdata_interaction_s sPropdataInteractionSections[PROPINTER_NUM_INTERACTIONS] =
-{
- { "physgun_interactions", "onworldimpact", "stick" }, // PROPINTER_PHYSGUN_WORLD_STICK,
- { "physgun_interactions", "onfirstimpact", "break" }, // PROPINTER_PHYSGUN_FIRST_BREAK,
- { "physgun_interactions", "onfirstimpact", "paintsplat" }, // PROPINTER_PHYSGUN_FIRST_PAINT,
- { "physgun_interactions", "onfirstimpact", "impale" }, // PROPINTER_PHYSGUN_FIRST_IMPALE,
- { "physgun_interactions", "onlaunch", "spin_none" }, // PROPINTER_PHYSGUN_LAUNCH_SPIN_NONE,
- { "physgun_interactions", "onlaunch", "spin_zaxis" }, // PROPINTER_PHYSGUN_LAUNCH_SPIN_Z,
- { "physgun_interactions", "onbreak", "explode_fire" }, // PROPINTER_PHYSGUN_BREAK_EXPLODE,
- { "physgun_interactions", "damage", "none" }, // PROPINTER_PHYSGUN_DAMAGE_NONE,
-
- { "fire_interactions", "flammable", "yes" }, // PROPINTER_FIRE_FLAMMABLE,
- { "fire_interactions", "explosive_resist", "yes" }, // PROPINTER_FIRE_EXPLOSIVE_RESIST,
- { "fire_interactions", "ignite", "halfhealth" }, // PROPINTER_FIRE_IGNITE_HALFHEALTH,
-
- { "physgun_interactions", "onpickup", "create_flare" }, // PROPINTER_PHYSGUN_CREATE_FLARE,
-
- { "physgun_interactions", "allow_overhead", "yes" }, // PROPINTER_PHYSGUN_ALLOW_OVERHEAD,
-
- { "world_interactions", "onworldimpact", "bloodsplat" }, // PROPINTER_WORLD_BLOODSPLAT,
-};
-#else
-extern propdata_interaction_s sPropdataInteractionSections[PROPINTER_NUM_INTERACTIONS];
-#endif
-
-//-----------------------------------------------------------------------------
-// Constructor, destructor
-//-----------------------------------------------------------------------------
-CPropData::CPropData( void ) :
- CAutoGameSystem( "CPropData" )
-{
- m_bPropDataLoaded = false;
- m_pKVPropData = NULL;
-}
-
-//-----------------------------------------------------------------------------
-// Inherited from IAutoServerSystem
-//-----------------------------------------------------------------------------
-void CPropData::LevelInitPreEntity( void )
-{
- m_BreakableChunks.RemoveAll();
- ParsePropDataFile();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CPropData::LevelShutdownPostEntity( void )
-{
- if ( m_pKVPropData )
- {
- m_pKVPropData->deleteThis();
- m_pKVPropData = NULL;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Clear out the stats + their history
-//-----------------------------------------------------------------------------
-void CPropData::ParsePropDataFile( void )
-{
- m_pKVPropData = new KeyValues( "PropDatafile" );
- if ( !m_pKVPropData->LoadFromFile( filesystem, "scripts/propdata.txt" ) )
- {
- m_pKVPropData->deleteThis();
- m_pKVPropData = NULL;
- return;
- }
-
- m_bPropDataLoaded = true;
-
- // Now try and parse out the breakable section
- KeyValues *pBreakableSection = m_pKVPropData->FindKey( "BreakableModels" );
- if ( pBreakableSection )
- {
- KeyValues *pChunkSection = pBreakableSection->GetFirstSubKey();
- while ( pChunkSection )
- {
- // Create a new chunk section and add it to our list
- int index = m_BreakableChunks.AddToTail();
- propdata_breakablechunk_t *pBreakableChunk = &m_BreakableChunks[index];
- pBreakableChunk->iszChunkType = AllocPooledString( pChunkSection->GetName() );
-
- // Read in all the model names
- KeyValues *pModelName = pChunkSection->GetFirstSubKey();
- while ( pModelName )
- {
- const char *pModel = pModelName->GetName();
- string_t pooledName = AllocPooledString( pModel );
- pBreakableChunk->iszChunkModels.AddToTail( pooledName );
- CBaseEntity::PrecacheModel( STRING( pooledName ) );
-
- pModelName = pModelName->GetNextKey();
- }
-
- pChunkSection = pChunkSection->GetNextKey();
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Parse a keyvalues section into the prop
-//
-// pInteractionSection is a bit of jiggery-pokery to get around the unfortunate
-// fact that the interaction KV sections ("physgun_interactions", "fire_interactions", etc)
-// are OUTSIDE the "prop_data" KV section in the model, but may be contained WITHIN the
-// specified Base's "prop_data" section (i.e. in propdata.txt)
-//-----------------------------------------------------------------------------
-int CPropData::ParsePropFromKV( CBaseEntity *pProp, KeyValues *pSection, KeyValues *pInteractionSection )
-{
- IBreakableWithPropData *pBreakableInterface = dynamic_cast<IBreakableWithPropData*>(pProp);
- if ( !pBreakableInterface )
- return PARSE_FAILED_BAD_DATA;
-
- if ( !pBreakableInterface )
- return PARSE_FAILED_BAD_DATA;
-
- int iBaseResult = PARSE_SUCCEEDED;
-
- // Do we have a base?
- char const *pszBase = pSection->GetString( "base" );
- if ( pszBase && pszBase[0] )
- {
- iBaseResult = ParsePropFromBase( pProp, pszBase );
- if ( (iBaseResult != PARSE_SUCCEEDED) && (iBaseResult != PARSE_SUCCEEDED_ALLOWED_STATIC) )
- return iBaseResult;
- }
-
- // Allow overriding of Block LOS
- int iBlockLOS = pSection->GetFloat( "blockLOS", -1 );
- if ( iBlockLOS != -1 )
- {
- pBreakableInterface->SetPropDataBlocksLOS( iBlockLOS != 0 );
- }
-
- // Set whether AI can walk on this prop
- int iIsWalkable = pSection->GetFloat( "AIWalkable", -1 );
- if ( iIsWalkable != -1 )
- {
- pBreakableInterface->SetPropDataIsAIWalkable( iIsWalkable != 0 );
- }
-
- // Set custom damage table
- const char *pszTableName;
- if ( pBreakableInterface->GetPhysicsDamageTable() == NULL_STRING )
- {
- pszTableName = pSection->GetString( "damage_table", NULL );
- }
- else
- {
- pszTableName = pSection->GetString( "damage_table", STRING(pBreakableInterface->GetPhysicsDamageTable()) );
- }
- if ( pszTableName && pszTableName[0] )
- {
- pBreakableInterface->SetPhysicsDamageTable( AllocPooledString( pszTableName ) );
- }
- else
- {
- pBreakableInterface->SetPhysicsDamageTable( NULL_STRING );
- }
-
- // Get multiplayer physics mode if not set by map
- pBreakableInterface->SetPhysicsMode( pSection->GetInt( "physicsmode",
- pBreakableInterface->GetPhysicsMode() ) );
-
- const char *multiplayer_break = pSection->GetString( "multiplayer_break", NULL );
- if ( multiplayer_break )
- {
- mp_break_t mode = MULTIPLAYER_BREAK_DEFAULT;
- if ( FStrEq( multiplayer_break, "server" ) )
- {
- mode = MULTIPLAYER_BREAK_SERVERSIDE;
- }
- else if ( FStrEq( multiplayer_break, "client" ) )
- {
- mode = MULTIPLAYER_BREAK_CLIENTSIDE;
- }
- else if ( FStrEq( multiplayer_break, "both" ) )
- {
- mode = MULTIPLAYER_BREAK_BOTH;
- }
- pBreakableInterface->SetMultiplayerBreakMode( mode );
- }
-
- // Get damage modifiers, but only if they're specified, because our base may have already overridden them.
- pBreakableInterface->SetDmgModBullet( pSection->GetFloat( "dmg.bullets", pBreakableInterface->GetDmgModBullet() ) );
- pBreakableInterface->SetDmgModClub( pSection->GetFloat( "dmg.club", pBreakableInterface->GetDmgModClub() ) );
- pBreakableInterface->SetDmgModExplosive( pSection->GetFloat( "dmg.explosive", pBreakableInterface->GetDmgModExplosive() ) );
-
- // Get the health (unless this is an override prop)
- if ( !FClassnameIs( pProp, "prop_physics_override" ) && !FClassnameIs( pProp, "prop_dynamic_override" ) )
- {
- pProp->SetHealth( pSection->GetInt( "health", pProp->GetHealth() ) );
-
- // Explosive?
- pBreakableInterface->SetExplosiveDamage( pSection->GetFloat( "explosive_damage", pBreakableInterface->GetExplosiveDamage() ) );
- pBreakableInterface->SetExplosiveRadius( pSection->GetFloat( "explosive_radius", pBreakableInterface->GetExplosiveRadius() ) );
-
-#ifdef GAME_DLL
- // If we now have health, we're not allowed to ignore physics damage
- if ( pProp->GetHealth() )
- {
- pProp->RemoveSpawnFlags( SF_PHYSPROP_DONT_TAKE_PHYSICS_DAMAGE );
- }
-#endif
- }
-
- const char *pszBreakableModel;
- if ( pBreakableInterface->GetBreakableModel() == NULL_STRING )
- {
- pszBreakableModel = pSection->GetString( "breakable_model", NULL );
- }
- else
- {
- pszBreakableModel = pSection->GetString( "breakable_model", STRING(pBreakableInterface->GetBreakableModel()) );
- }
- if ( pszBreakableModel && pszBreakableModel[0] )
- {
- pBreakableInterface->SetBreakableModel( AllocPooledString( pszBreakableModel ) );
- }
- else
- {
- pBreakableInterface->SetBreakableModel( NULL_STRING );
- }
- pBreakableInterface->SetBreakableSkin( pSection->GetInt( "breakable_skin", pBreakableInterface->GetBreakableSkin() ) );
- pBreakableInterface->SetBreakableCount( pSection->GetInt( "breakable_count", pBreakableInterface->GetBreakableCount() ) );
-
- // Calculate the maximum size of the breakables this breakable will produce
- Vector vecSize = pProp->CollisionProp()->OBBSize();
- // Throw away the smallest coord
- int iSmallest = SmallestAxis(vecSize);
- vecSize[iSmallest] = 1;
- float flVolume = vecSize.x * vecSize.y * vecSize.z;
- int iMaxSize = floor( flVolume / (32.0*32.0) );
- pBreakableInterface->SetMaxBreakableSize( iMaxSize );
-
- // Now parse our interactions
- for ( int i = 0; i < PROPINTER_NUM_INTERACTIONS; i++ )
- {
- // If we hit this assert, we have too many interactions for our current storage solution to handle
- Assert( i < 32 );
-
- propdata_interaction_s *pInteraction = &sPropdataInteractionSections[i];
-
- KeyValues *pkvCurrentInter = pInteractionSection->FindKey( pInteraction->pszSectionName );
- if ( pkvCurrentInter )
- {
- char const *pszInterBase = pkvCurrentInter->GetString( pInteraction->pszKeyName );
- if ( pszInterBase && pszInterBase[0] && !stricmp( pszInterBase, pInteraction->pszValue ) )
- {
- pBreakableInterface->SetInteraction( (propdata_interactions_t)i );
- }
- }
- }
-
- // If the base said we're allowed to be static, return that
- if ( iBaseResult == PARSE_SUCCEEDED_ALLOWED_STATIC )
- return PARSE_SUCCEEDED_ALLOWED_STATIC;
-
- // Otherwise, see if our propdata says we are allowed to be static
- if ( pSection->GetInt( "allowstatic", 0 ) )
- return PARSE_SUCCEEDED_ALLOWED_STATIC;
-
- return PARSE_SUCCEEDED;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Fill out a prop's with base data parsed from the propdata file
-//-----------------------------------------------------------------------------
-int CPropData::ParsePropFromBase( CBaseEntity *pProp, const char *pszPropData )
-{
- if ( !m_bPropDataLoaded )
- return PARSE_FAILED_NO_DATA;
-
- IBreakableWithPropData *pBreakableInterface = dynamic_cast<IBreakableWithPropData*>(pProp);
-
- if ( !pBreakableInterface )
- {
- return PARSE_FAILED_BAD_DATA;
- }
-
- if ( !m_pKVPropData )
- {
- return PARSE_FAILED_BAD_DATA;
- }
-
- // Find the specified propdata
- KeyValues *pSection = m_pKVPropData->FindKey( pszPropData );
- if ( !pSection )
- {
- 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 );
- return PARSE_FAILED_BAD_DATA;
- }
-
- // Store off the first base data for debugging
- if ( pBreakableInterface->GetBasePropData() == NULL_STRING )
- {
- pBreakableInterface->SetBasePropData( AllocPooledString( pszPropData ) );
- }
-
- return ParsePropFromKV( pProp, pSection, pSection );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-const char *CPropData::GetRandomChunkModel( const char *pszBreakableSection, int iMaxSize )
-{
- if ( !m_bPropDataLoaded )
- return NULL;
-
- // Find the right section
- int iCount = m_BreakableChunks.Count();
- int i;
- for ( i = 0; i < iCount; i++ )
- {
- if ( !Q_strncmp( STRING(m_BreakableChunks[i].iszChunkType), pszBreakableSection, strlen(pszBreakableSection) ) )
- break;
- }
- if ( i == iCount )
- return NULL;
-
- // Now pick a random one and return it
- int iRandom;
- if ( iMaxSize == -1 )
- {
- iRandom = RandomInt( 0, m_BreakableChunks[i].iszChunkModels.Count()-1 );
- }
- else
- {
- // Don't pick anything over the specified size
- iRandom = RandomInt( 0, MIN(iMaxSize, m_BreakableChunks[i].iszChunkModels.Count()-1) );
- }
-
- return STRING(m_BreakableChunks[i].iszChunkModels[iRandom]);
-}
-
-
-// ensure that a model name from a qc file is properly formatted
-static const char *FixupModelName( char *pOut, int sizeOut, const char *pModelNameIn )
-{
- char tmp[1024];
-
- Q_strncpy( tmp, pModelNameIn, sizeof(tmp) );
- if ( Q_strnicmp( tmp, "models/", 7 ) )
- {
- Q_snprintf( pOut, sizeOut, "models/%s" , tmp );
- }
- else
- {
- Q_strncpy( pOut, tmp, sizeOut);
- }
- int len = Q_strlen(pOut);
- if ( len < 4 || Q_stricmp( pOut + (len-4), ".mdl" ) )
- {
- Q_strncat( pOut, ".mdl", sizeOut, COPY_ALL_CHARACTERS );
- }
-
- return pOut;
-}
-
-
-//-----------------------------------------------------------------------------
-// breakable prop functions
-//-----------------------------------------------------------------------------
-//
-//-----------------------------------------------------------------------------
-// list of models to break into
-class CBreakParser : public IVPhysicsKeyHandler
-{
-public:
- CBreakParser( float defaultBurstScale, int defaultCollisionGroup )
- : m_defaultBurstScale(defaultBurstScale), m_defaultCollisionGroup(defaultCollisionGroup) {}
-
- void ParseModelName( breakmodel_t *pModel, const char *pValue )
- {
- FixupModelName( pModel->modelName, sizeof(pModel->modelName), pValue );
- }
- virtual void ParseKeyValue( void *pData, const char *pKey, const char *pValue )
- {
- breakmodel_t *pModel = (breakmodel_t *)pData;
- if ( !strcmpi( pKey, "model" ) )
- {
- ParseModelName( pModel, pValue );
- }
- else if (!strcmpi( pKey, "ragdoll" ) )
- {
- ParseModelName( pModel, pValue );
- pModel->isRagdoll = true;
- }
- else if (!strcmpi( pKey, "motiondisabled" ) )
- {
- pModel->isMotionDisabled = true;
- }
- else if ( !strcmpi( pKey, "offset" ) )
- {
- UTIL_StringToVector( pModel->offset.Base(), pValue );
- }
- else if ( !strcmpi( pKey, "health" ) )
- {
- pModel->health = atof(pValue);
- }
- else if ( !strcmpi( pKey, "fadetime" ) )
- {
- pModel->fadeTime = atof(pValue);
- if ( !m_wroteCollisionGroup )
- {
- pModel->collisionGroup = COLLISION_GROUP_DEBRIS;
- }
- }
- else if ( !strcmpi( pKey, "fademindist" ) )
- {
- pModel->fadeMinDist = atof(pValue);
- }
- else if ( !strcmpi( pKey, "fademaxdist" ) )
- {
- pModel->fadeMaxDist = atof(pValue);
- }
- else if ( !strcmpi( pKey, "debris" ) )
- {
- pModel->collisionGroup = atoi(pValue) > 0 ? COLLISION_GROUP_DEBRIS : COLLISION_GROUP_INTERACTIVE;
- m_wroteCollisionGroup = true;
- }
- else if ( !strcmpi( pKey, "burst" ) )
- {
- pModel->burstScale = atof( pValue );
- }
- else if ( !strcmpi( pKey, "placementbone" ) )
- {
- Q_strncpy( pModel->placementName, pValue, sizeof(pModel->placementName) );
- pModel->placementIsBone = true;
- }
- else if ( !strcmpi( pKey, "placementattachment" ) )
- {
- Q_strncpy( pModel->placementName, pValue, sizeof(pModel->placementName) );
- pModel->placementIsBone = false;
- }
- else if ( !strcmpi( pKey, "multiplayer_break" ) )
- {
- if ( FStrEq( pValue, "server" ) )
- {
- pModel->mpBreakMode = MULTIPLAYER_BREAK_SERVERSIDE;
- }
- else if ( FStrEq( pValue, "client" ) )
- {
- pModel->mpBreakMode = MULTIPLAYER_BREAK_CLIENTSIDE;
- }
- }
- }
- virtual void SetDefaults( void *pData )
- {
- breakmodel_t *pModel = (breakmodel_t *)pData;
- pModel->modelName[0] = 0;
- pModel->offset = vec3_origin;
- pModel->health = 1;
- pModel->fadeTime = 20.0f;
- pModel->fadeMinDist = 0.0f;
- pModel->fadeMaxDist = 0.0f;
- pModel->burstScale = m_defaultBurstScale;
- pModel->collisionGroup = m_defaultCollisionGroup;
- pModel->isRagdoll = false;
- pModel->isMotionDisabled = false;
- pModel->placementName[0] = 0;
- pModel->placementIsBone = false;
- pModel->mpBreakMode = MULTIPLAYER_BREAK_DEFAULT;
- m_wroteCollisionGroup = false;
- }
-
-private:
- int m_defaultCollisionGroup;
- float m_defaultBurstScale;
- bool m_wroteCollisionGroup;
-};
-
-void BreakModelList( CUtlVector<breakmodel_t> &list, int modelindex, float defBurstScale, int defCollisionGroup )
-{
- vcollide_t *pCollide = modelinfo->GetVCollide( modelindex );
- if ( !pCollide )
- return;
-
- IVPhysicsKeyParser *pParse = physcollision->VPhysicsKeyParserCreate( pCollide->pKeyValues );
- while ( !pParse->Finished() )
- {
- CBreakParser breakParser( defBurstScale, defCollisionGroup );
-
- const char *pBlock = pParse->GetCurrentBlockName();
- if ( !strcmpi( pBlock, "break" ) )
- {
- int index = list.AddToTail();
- breakmodel_t &breakModel = list[index];
- pParse->ParseCustom( &breakModel, &breakParser );
- }
- else
- {
- pParse->SkipBlock();
- }
- }
- physcollision->VPhysicsKeyParserDestroy( pParse );
-}
-
-#if !defined(_STATIC_LINKED) || defined(CLIENT_DLL)
-int GetAutoMultiplayerPhysicsMode( Vector size, float mass )
-{
- float volume = size.x * size.y * size.z;
-
- float minsize = sv_pushaway_clientside_size.GetFloat();
-
- // if it's too small, client side only
- if ( volume < (minsize*minsize*minsize) )
- return PHYSICS_MULTIPLAYER_CLIENTSIDE;
-
- // if it's too light, no player pushback
- if ( mass < 8.0 )
- return PHYSICS_MULTIPLAYER_NON_SOLID;
-
- // full pushbackmode
- return PHYSICS_MULTIPLAYER_SOLID;
-}
-#else
-extern int GetAutoMultiplayerPhysicsMode( Vector size, float mass );
-#endif
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns a string describing a real-world equivalent mass.
-// Input : flMass - mass in kg
-//-----------------------------------------------------------------------------
-#if !defined(_STATIC_LINKED) || defined(CLIENT_DLL)
-const char *GetMassEquivalent(float flMass)
-{
- static struct
- {
- float flMass;
- const char *sz;
- } masstext[] =
- {
- { 5e-6, "snowflake" },
- { 2.5e-3, "ping-pong ball" },
- { 5e-3, "penny" },
- { 0.05, "golf ball" },
- { 0.17, "billard ball" },
- { 2, "bag of sugar" },
- { 7, "male cat" },
- { 10, "bowling ball" },
- { 30, "dog" },
- { 60, "cheetah" },
- { 90, "adult male human" },
- { 250, "refrigerator" },
- { 600, "race horse" },
- { 1000, "small car" },
- { 1650, "medium car" },
- { 2500, "large car" },
- { 6000, "t-rex" },
- { 7200, "elephant" },
- { 8e4, "space shuttle" },
- { 7e5, "locomotive" },
- { 9.2e6, "Eiffel tower" },
- { 6e24, "the Earth" },
- { 7e24, "really freaking heavy" },
- };
-
- for (int i = 0; i < sizeof(masstext) / sizeof(masstext[0]) - 1; i++)
- {
- if (flMass < masstext[i].flMass)
- {
- return masstext[i].sz;
- }
- }
-
- return masstext[ sizeof(masstext) / sizeof(masstext[0]) - 1 ].sz;
-}
-#else
-extern const char *GetMassEquivalent(float flMass);
-#endif
-
-#ifdef GAME_DLL
-//=========================================================
-//=========================================================
-class CGameGibManager : public CBaseEntity
-{
- DECLARE_CLASS( CGameGibManager, CBaseEntity );
- DECLARE_DATADESC();
-
-public:
-
- CGameGibManager() : m_iCurrentMaxPieces(-1), m_iMaxPieces(-1), m_iMaxPiecesDX8(-1) {}
-
- void Activate( void );
- void AddGibToLRU( CBaseAnimating *pEntity );
-
- inline bool AllowedToSpawnGib( void );
-
-private:
-
- void UpdateMaxPieces();
-
- void InputSetMaxPieces( inputdata_t &inputdata );
- void InputSetMaxPiecesDX8( inputdata_t &inputdata );
-
- typedef CHandle<CBaseAnimating> CGibHandle;
- CUtlLinkedList< CGibHandle > m_LRU;
-
- bool m_bAllowNewGibs;
-
- int m_iDXLevel;
- int m_iCurrentMaxPieces;
- int m_iMaxPieces;
- int m_iMaxPiecesDX8;
- int m_iLastFrame;
-};
-
-BEGIN_DATADESC( CGameGibManager )
- // Silence perfidous classcheck!
- //DEFINE_FIELD( m_iCurrentMaxPieces, FIELD_INTEGER ),
- //DEFINE_FIELD( m_iLastFrame, FIELD_INTEGER ),
- //DEFINE_FIELD( m_iDXLevel, FIELD_INTEGER ),
- DEFINE_KEYFIELD( m_iMaxPieces, FIELD_INTEGER, "maxpieces" ),
- DEFINE_KEYFIELD( m_iMaxPiecesDX8, FIELD_INTEGER, "maxpiecesdx8" ),
- DEFINE_KEYFIELD( m_bAllowNewGibs, FIELD_BOOLEAN, "allownewgibs" ),
-
- DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMaxPieces", InputSetMaxPieces ),
- DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMaxPiecesDX8", InputSetMaxPiecesDX8 ),
-END_DATADESC()
-
-LINK_ENTITY_TO_CLASS( game_gib_manager, CGameGibManager );
-
-
-void CGameGibManager::Activate( void )
-{
- m_LRU.Purge();
-
- // Cache off the DX level for use later.
- ConVarRef mat_dxlevel( "mat_dxlevel" );
- m_iDXLevel = mat_dxlevel.GetInt();
-
- UpdateMaxPieces();
-
- BaseClass::Activate();
-}
-
-void CGameGibManager::UpdateMaxPieces()
-{
- // If we're running DX8, use the DX8 gib limit if set.
- if ( ( m_iDXLevel < 90 ) && ( m_iMaxPiecesDX8 >= 0 ) )
- {
- m_iCurrentMaxPieces = m_iMaxPiecesDX8;
- }
- else
- {
- m_iCurrentMaxPieces = m_iMaxPieces;
- }
-}
-
-
-bool CGameGibManager::AllowedToSpawnGib( void )
-{
- if ( m_bAllowNewGibs )
- return true;
-
- // We're not tracking gibs at the moment
- if ( m_iCurrentMaxPieces < 0 )
- return true;
-
- if ( m_iCurrentMaxPieces == 0 )
- return false;
-
- if ( m_iLastFrame == gpGlobals->framecount )
- {
- if ( m_LRU.Count() >= m_iCurrentMaxPieces )
- {
- return false;
- }
- }
-
- return true;
-}
-
-void CGameGibManager::InputSetMaxPieces( inputdata_t &inputdata )
-{
- m_iMaxPieces = inputdata.value.Int();
- UpdateMaxPieces();
-}
-
-void CGameGibManager::InputSetMaxPiecesDX8( inputdata_t &inputdata )
-{
- m_iMaxPiecesDX8 = inputdata.value.Int();
- UpdateMaxPieces();
-}
-
-void CGameGibManager::AddGibToLRU( CBaseAnimating *pEntity )
-{
- int i, next;
-
- if ( pEntity == NULL )
- return;
-
- //Find stale gibs.
- for ( i = m_LRU.Head(); i < m_LRU.InvalidIndex(); i = next )
- {
- next = m_LRU.Next(i);
-
- if ( m_LRU[i].Get() == NULL )
- {
- m_LRU.Remove(i);
- }
- }
-
- // We're not tracking gibs at the moment
- if ( m_iCurrentMaxPieces <= 0 )
- return;
-
- while ( m_LRU.Count() >= m_iCurrentMaxPieces )
- {
- i = m_LRU.Head();
-
- //TODO: Make this fade out instead of pop.
- UTIL_Remove( m_LRU[i] );
- m_LRU.Remove(i);
- }
-
- m_LRU.AddToTail( pEntity );
- m_iLastFrame = gpGlobals->framecount;
-}
-
-EHANDLE g_hGameGibManager;
-
-CGameGibManager *GetGibManager( void )
-{
-#ifndef HL2_EPISODIC
- return NULL;
-#endif
-
- if ( g_hGameGibManager == NULL )
- {
- g_hGameGibManager = (CGameGibManager *)gEntList.FindEntityByClassname( NULL, "game_gib_manager" );
- }
-
- return (CGameGibManager *)g_hGameGibManager.Get();
-}
-
-#endif
-
-void PropBreakableCreateAll( int modelindex, IPhysicsObject *pPhysics, const breakablepropparams_t &params, CBaseEntity *pEntity, int iPrecomputedBreakableCount, bool bIgnoreGibLimit, bool defaultLocation )
-{
- // Check for prop breakable count reset.
- int nPropCount = props_break_max_pieces_perframe.GetInt();
- if ( nPropCount != -1 )
- {
- if ( nFrameNumber != gpGlobals->framecount )
- {
- nPropBreakablesPerFrameCount = 0;
- nFrameNumber = gpGlobals->framecount;
- }
-
- // Check for max breakable count for the frame.
- if ( nPropBreakablesPerFrameCount >= nPropCount )
- return;
- }
-
- int iMaxBreakCount = bIgnoreGibLimit ? -1 : props_break_max_pieces.GetInt();
- if ( iMaxBreakCount != -1 )
- {
- if ( iPrecomputedBreakableCount != -1 )
- {
- iPrecomputedBreakableCount = MIN( iMaxBreakCount, iPrecomputedBreakableCount );
- }
- else
- {
- iPrecomputedBreakableCount = iMaxBreakCount;
- }
- }
-
-#ifdef GAME_DLL
- // On server limit break model creation
- if ( !PropBreakableCapEdictsOnCreateAll(modelindex, pPhysics, params, pEntity, iPrecomputedBreakableCount ) )
- {
- DevMsg( "Failed to create PropBreakable: would exceed MAX_EDICTS\n" );
- return;
- }
-#endif
-
- vcollide_t *pCollide = modelinfo->GetVCollide( modelindex );
- if ( !pCollide )
- return;
-
- int nSkin = 0;
- CBaseEntity *pOwnerEntity = pEntity;
- CBaseAnimating *pOwnerAnim = NULL;
- if ( pPhysics )
- {
- pOwnerEntity = static_cast<CBaseEntity *>(pPhysics->GetGameData());
- }
- if ( pOwnerEntity )
- {
- pOwnerAnim = pOwnerEntity->GetBaseAnimating();
- if ( pOwnerAnim )
- {
- nSkin = pOwnerAnim->m_nSkin;
- }
- }
- matrix3x4_t localToWorld;
-
- CStudioHdr studioHdr;
- const model_t *model = modelinfo->GetModel( modelindex );
- if ( model )
- {
- studioHdr.Init( modelinfo->GetStudiomodel( model ) );
- }
-
- Vector parentOrigin = vec3_origin;
- int parentAttachment = Studio_FindAttachment( &studioHdr, "placementOrigin" ) + 1;
- if ( parentAttachment > 0 )
- {
- GetAttachmentLocalSpace( &studioHdr, parentAttachment-1, localToWorld );
- MatrixGetColumn( localToWorld, 3, parentOrigin );
- }
- else
- {
- AngleMatrix( vec3_angle, localToWorld );
- }
-
- CUtlVector<breakmodel_t> list;
-
- BreakModelList( list, modelindex, params.defBurstScale, params.defCollisionGroup );
-
- if ( list.Count() )
- {
- for ( int i = 0; i < list.Count(); i++ )
- {
- int modelIndex = modelinfo->GetModelIndex( list[i].modelName );
- if ( modelIndex <= 0 )
- continue;
-
- // Skip multiplayer pieces that should be spawning on the other dll
-#ifdef GAME_DLL
- if ( gpGlobals->maxClients > 1 && breakable_multiplayer.GetBool() )
-#else
- if ( gpGlobals->maxClients > 1 )
-#endif
- {
-#ifdef GAME_DLL
- if ( list[i].mpBreakMode == MULTIPLAYER_BREAK_CLIENTSIDE )
- continue;
-#else
- if ( list[i].mpBreakMode == MULTIPLAYER_BREAK_SERVERSIDE )
- continue;
-#endif
-
- if ( !defaultLocation && list[i].mpBreakMode == MULTIPLAYER_BREAK_DEFAULT )
- continue;
- }
-
- if ( ( nPropCount != -1 ) && ( nPropBreakablesPerFrameCount > nPropCount ) )
- break;
-
- if ( ( iPrecomputedBreakableCount != -1 ) && ( i >= iPrecomputedBreakableCount ) )
- break;
-
- matrix3x4_t matrix;
- AngleMatrix( params.angles, params.origin, matrix );
-
- CStudioHdr studioHdr;
- const model_t *model = modelinfo->GetModel( modelIndex );
- if ( model )
- {
- studioHdr.Init( modelinfo->GetStudiomodel( model ) );
- }
-
- // Increment the number of breakable props this frame.
- ++nPropBreakablesPerFrameCount;
-
- Vector position = vec3_origin;
- QAngle angles = params.angles;
- if ( pOwnerAnim && list[i].placementName[0] )
- {
- if ( list[i].placementIsBone )
- {
- int boneIndex = pOwnerAnim->LookupBone( list[i].placementName );
- if ( boneIndex >= 0 )
- {
- pOwnerAnim->GetBonePosition( boneIndex, position, angles );
- AngleMatrix( angles, position, matrix );
- }
- }
- else
- {
- int attachmentIndex = Studio_FindAttachment( &studioHdr, list[i].placementName ) + 1;
- if ( attachmentIndex > 0 )
- {
- pOwnerAnim->GetAttachment( attachmentIndex, matrix );
- MatrixAngles( matrix, angles );
- }
- }
- }
- else
- {
- int placementIndex = Studio_FindAttachment( &studioHdr, "placementOrigin" ) + 1;
- Vector placementOrigin = parentOrigin;
- if ( placementIndex > 0 )
- {
- GetAttachmentLocalSpace( &studioHdr, placementIndex-1, localToWorld );
- MatrixGetColumn( localToWorld, 3, placementOrigin );
- placementOrigin -= parentOrigin;
- }
-
- VectorTransform( list[i].offset - placementOrigin, matrix, position );
- }
- Vector objectVelocity = params.velocity;
-
- if (pPhysics)
- {
- pPhysics->GetVelocityAtPoint( position, &objectVelocity );
- }
-
- int nActualSkin = nSkin;
- if ( nActualSkin > studioHdr.numskinfamilies() )
- nActualSkin = 0;
-
- CBaseEntity *pBreakable = NULL;
-
-#ifdef GAME_DLL
- if ( GetGibManager() == NULL || GetGibManager()->AllowedToSpawnGib() )
-#endif
- {
- pBreakable = BreakModelCreateSingle( pOwnerEntity, &list[i], position, angles, objectVelocity, params.angularVelocity, nActualSkin, params );
- }
-
- if ( pBreakable )
- {
-#ifdef GAME_DLL
- if ( GetGibManager() )
- {
- GetGibManager()->AddGibToLRU( pBreakable->GetBaseAnimating() );
- }
-#endif
- if ( pOwnerEntity && pOwnerEntity->IsEffectActive( EF_NOSHADOW ) )
- {
- pBreakable->AddEffects( EF_NOSHADOW );
- }
-
- // If burst scale is set, this piece should 'burst' away from
- // the origin in addition to travelling in the wished velocity.
- if ( list[i].burstScale != 0.0 )
- {
- Vector vecBurstDir = position - params.origin;
-
- // If $autocenter wasn't used, try the center of the piece
- if ( vecBurstDir == vec3_origin )
- {
- vecBurstDir = pBreakable->WorldSpaceCenter() - params.origin;
- }
-
- VectorNormalize( vecBurstDir );
-
- pBreakable->ApplyAbsVelocityImpulse( vecBurstDir * list[i].burstScale );
- }
-
- // If this piece is supposed to be motion disabled, disable it
- if ( list[i].isMotionDisabled )
- {
- IPhysicsObject *pPhysicsObject = pBreakable->VPhysicsGetObject();
- if ( pPhysicsObject != NULL )
- {
- pPhysicsObject->EnableMotion( false );
- }
- }
- }
- }
- }
- // Then see if the propdata specifies any breakable pieces
- else if ( pEntity )
- {
- IBreakableWithPropData *pBreakableInterface = dynamic_cast<IBreakableWithPropData*>(pEntity);
- if ( pBreakableInterface && pBreakableInterface->GetBreakableModel() != NULL_STRING && pBreakableInterface->GetBreakableCount() )
- {
- breakmodel_t breakModel;
-
- for ( int i = 0; i < pBreakableInterface->GetBreakableCount(); i++ )
- {
- if ( ( iPrecomputedBreakableCount != -1 ) && ( i >= iPrecomputedBreakableCount ) )
- break;
-
- Q_strncpy( breakModel.modelName, g_PropDataSystem.GetRandomChunkModel(STRING(pBreakableInterface->GetBreakableModel()), pBreakableInterface->GetMaxBreakableSize()), sizeof(breakModel.modelName) );
-
- breakModel.health = 1;
- breakModel.fadeTime = RandomFloat(5,10);
- breakModel.fadeMinDist = 0.0f;
- breakModel.fadeMaxDist = 0.0f;
- breakModel.burstScale = params.defBurstScale;
- breakModel.collisionGroup = COLLISION_GROUP_DEBRIS;
- breakModel.isRagdoll = false;
- breakModel.isMotionDisabled = false;
- breakModel.placementName[0] = 0;
- breakModel.placementIsBone = false;
-
- Vector vecObbSize = pEntity->CollisionProp()->OBBSize();
-
- // Find a random point on the plane of the original's two largest axis
- int smallestAxis = SmallestAxis( vecObbSize );
- Vector vecMins(0,0,0);
- Vector vecMaxs(1,1,1);
- vecMins[smallestAxis] = 0.5;
- vecMaxs[smallestAxis] = 0.5;
- pEntity->CollisionProp()->RandomPointInBounds( vecMins, vecMaxs, &breakModel.offset );
-
- // Push all chunks away from the center
- Vector vecBurstDir = breakModel.offset - params.origin;
- VectorNormalize( vecBurstDir );
- Vector vecVelocity = vecBurstDir * params.defBurstScale;
-
- QAngle vecAngles = pEntity->GetAbsAngles();
- int iSkin = pBreakableInterface->GetBreakableSkin();
-
- CBaseEntity *pBreakable = NULL;
-
-#ifdef GAME_DLL
- if ( GetGibManager() == NULL || GetGibManager()->AllowedToSpawnGib() )
-#endif
- {
- pBreakable = BreakModelCreateSingle( pOwnerEntity, &breakModel, breakModel.offset, vecAngles, vecVelocity, vec3_origin/*params.angularVelocity*/, iSkin, params );
- if ( !pBreakable )
- {
- DevWarning( "PropBreakableCreateAll: Could not create model %s\n", breakModel.modelName );
- }
- }
-
- if ( pBreakable )
- {
-#ifdef GAME_DLL
- if ( GetGibManager() )
- {
- GetGibManager()->AddGibToLRU( pBreakable->GetBaseAnimating() );
- }
-#endif
- Vector vecBreakableObbSize = pBreakable->CollisionProp()->OBBSize();
-
- // Try to align the gibs along the original axis
- matrix3x4_t matrix;
- AngleMatrix( vecAngles, matrix );
- AlignBoxes( &matrix, vecObbSize, vecBreakableObbSize );
- MatrixAngles( matrix, vecAngles );
-
- if ( pBreakable->VPhysicsGetObject() )
- {
- Vector pos;
- pBreakable->VPhysicsGetObject()->GetPosition( &pos, NULL );
- pBreakable->VPhysicsGetObject()->SetPosition( pos, vecAngles, true );
- }
-
- pBreakable->SetAbsAngles( vecAngles );
-
- if ( pOwnerEntity->IsEffectActive( EF_NOSHADOW ) )
- {
- pBreakable->AddEffects( EF_NOSHADOW );
- }
- }
- }
- }
- }
-}
-
-
-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 )
-{
- breakablepropparams_t params( origin, angles, velocity, angularVelocity );
- params.impactEnergyScale = impactEnergyScale;
- params.defBurstScale = defBurstScale;
- params.defCollisionGroup = defCollisionGroup;
- PropBreakableCreateAll( modelindex, pPhysics, params, pEntity, -1, false, defaultLocation );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : modelindex -
-//-----------------------------------------------------------------------------
-void PrecacheGibsForModel( int iModel )
-{
- VPROF_BUDGET( "PrecacheGibsForModel", VPROF_BUDGETGROUP_PLAYER );
- vcollide_t *pCollide = modelinfo->GetVCollide( iModel );
- if ( !pCollide )
- return;
-
- // The scale and group doesn't really matter at the moment, we are just using the parser to get the model name to cache.
- CBreakParser breakParser( 1.0, COLLISION_GROUP_NONE );
-
- // Create a parser.
- IVPhysicsKeyParser *pParse = physcollision->VPhysicsKeyParserCreate( pCollide->pKeyValues );
- while ( !pParse->Finished() )
- {
- const char *pBlock = pParse->GetCurrentBlockName();
- if ( !strcmpi( pBlock, "break" ) )
- {
- breakmodel_t breakModel;
- pParse->ParseCustom( &breakModel, &breakParser );
- CBaseEntity::PrecacheModel( breakModel.modelName );
- }
- else
- {
- pParse->SkipBlock();
- }
- }
-
- // Destroy the parser.
- physcollision->VPhysicsKeyParserDestroy( pParse );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : &list -
-// modelindex -
-// defBurstScale -
-// defCollisionGroup -
-//-----------------------------------------------------------------------------
-void BuildGibList( CUtlVector<breakmodel_t> &list, int modelindex, float defBurstScale, int defCollisionGroup )
-{
- BreakModelList( list, modelindex, defBurstScale, defCollisionGroup );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : &list -
-// modelindex -
-// *pPhysics -
-// &params -
-// *pEntity -
-// iPrecomputedBreakableCount -
-// bIgnoreGibLImit -
-// defaultLocation -
-//-----------------------------------------------------------------------------
-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 )
-{
- // Check for prop breakable count reset.
- int nPropCount = props_break_max_pieces_perframe.GetInt();
- if ( nPropCount != -1 )
- {
- if ( nFrameNumber != gpGlobals->framecount )
- {
- nPropBreakablesPerFrameCount = 0;
- nFrameNumber = gpGlobals->framecount;
- }
-
- // Check for max breakable count for the frame.
- if ( nPropBreakablesPerFrameCount >= nPropCount )
- return NULL;
- }
-
- int iMaxBreakCount = bIgnoreGibLimit ? -1 : props_break_max_pieces.GetInt();
- if ( iMaxBreakCount != -1 )
- {
- if ( iPrecomputedBreakableCount != -1 )
- {
- iPrecomputedBreakableCount = MIN( iMaxBreakCount, iPrecomputedBreakableCount );
- }
- else
- {
- iPrecomputedBreakableCount = iMaxBreakCount;
- }
- }
-
-#ifdef GAME_DLL
- // On server limit break model creation
- if ( !PropBreakableCapEdictsOnCreateAll(modelindex, pPhysics, params, pEntity, iPrecomputedBreakableCount ) )
- {
- DevMsg( "Failed to create PropBreakable: would exceed MAX_EDICTS\n" );
- return NULL;
- }
-#endif
-
- vcollide_t *pCollide = modelinfo->GetVCollide( modelindex );
- if ( !pCollide )
- return NULL;
-
- int nSkin = 0;
- CBaseEntity *pOwnerEntity = pEntity;
- CBaseAnimating *pOwnerAnim = NULL;
- if ( pPhysics )
- {
- pOwnerEntity = static_cast<CBaseEntity *>(pPhysics->GetGameData());
- }
- if ( pOwnerEntity )
- {
- pOwnerAnim = dynamic_cast<CBaseAnimating*>(pOwnerEntity);
- if ( pOwnerAnim )
- {
- nSkin = pOwnerAnim->m_nSkin;
- }
- }
- matrix3x4_t localToWorld;
-
- CStudioHdr studioHdr;
- const model_t *model = modelinfo->GetModel( modelindex );
- if ( model )
- {
- studioHdr.Init( modelinfo->GetStudiomodel( model ) );
- }
-
- Vector parentOrigin = vec3_origin;
- int parentAttachment = Studio_FindAttachment( &studioHdr, "placementOrigin" ) + 1;
- if ( parentAttachment > 0 )
- {
- GetAttachmentLocalSpace( &studioHdr, parentAttachment-1, localToWorld );
- MatrixGetColumn( localToWorld, 3, parentOrigin );
- }
- else
- {
- AngleMatrix( vec3_angle, localToWorld );
- }
-
-// CUtlVector<breakmodel_t> list;
-// BreakModelList( list, modelindex, params.defBurstScale, params.defCollisionGroup );
-
- CBaseEntity *pFirstBreakable = NULL;
-
- if ( list.Count() )
- {
- for ( int i = 0; i < list.Count(); i++ )
- {
- int modelIndex = modelinfo->GetModelIndex( list[i].modelName );
- if ( modelIndex <= 0 )
- continue;
-
- // Skip multiplayer pieces that should be spawning on the other dll
-#ifdef GAME_DLL
- if ( gpGlobals->maxClients > 1 && breakable_multiplayer.GetBool() )
-#else
- if ( gpGlobals->maxClients > 1 )
-#endif
- {
-#ifdef GAME_DLL
- if ( list[i].mpBreakMode == MULTIPLAYER_BREAK_CLIENTSIDE )
- continue;
-#else
- if ( list[i].mpBreakMode == MULTIPLAYER_BREAK_SERVERSIDE )
- continue;
-#endif
-
- if ( !defaultLocation && list[i].mpBreakMode == MULTIPLAYER_BREAK_DEFAULT )
- continue;
- }
-
- if ( ( nPropCount != -1 ) && ( nPropBreakablesPerFrameCount > nPropCount ) )
- break;
-
- if ( ( iPrecomputedBreakableCount != -1 ) && ( i >= iPrecomputedBreakableCount ) )
- break;
-
- matrix3x4_t matrix;
- AngleMatrix( params.angles, params.origin, matrix );
-
- CStudioHdr studioHdr;
- const model_t *model = modelinfo->GetModel( modelIndex );
- if ( model )
- {
- studioHdr.Init( modelinfo->GetStudiomodel( model ) );
- }
-
- // Increment the number of breakable props this frame.
- ++nPropBreakablesPerFrameCount;
-
- Vector position = vec3_origin;
- QAngle angles = params.angles;
- if ( pOwnerAnim && list[i].placementName[0] )
- {
- if ( list[i].placementIsBone )
- {
- int boneIndex = pOwnerAnim->LookupBone( list[i].placementName );
- if ( boneIndex >= 0 )
- {
- pOwnerAnim->GetBonePosition( boneIndex, position, angles );
- AngleMatrix( angles, position, matrix );
- }
- }
- else
- {
- int attachmentIndex = Studio_FindAttachment( &studioHdr, list[i].placementName ) + 1;
- if ( attachmentIndex > 0 )
- {
- pOwnerAnim->GetAttachment( attachmentIndex, matrix );
- MatrixAngles( matrix, angles );
- }
- }
- }
- else
- {
- int placementIndex = Studio_FindAttachment( &studioHdr, "placementOrigin" ) + 1;
- Vector placementOrigin = parentOrigin;
- if ( placementIndex > 0 )
- {
- GetAttachmentLocalSpace( &studioHdr, placementIndex-1, localToWorld );
- MatrixGetColumn( localToWorld, 3, placementOrigin );
- placementOrigin -= parentOrigin;
- }
-
- VectorTransform( list[i].offset - placementOrigin, matrix, position );
- }
- Vector objectVelocity = params.velocity;
-
- float flScale = VectorNormalize( objectVelocity );
- objectVelocity.x += RandomFloat( -1.f, 1.0f );
- objectVelocity.y += RandomFloat( -1.0f, 1.0f );
- objectVelocity.z += RandomFloat( 0.0f, 1.0f );
- VectorNormalize( objectVelocity );
- objectVelocity *= flScale;
-
- if (pPhysics)
- {
- pPhysics->GetVelocityAtPoint( position, &objectVelocity );
- }
-
- int nActualSkin = nSkin;
- if ( nActualSkin > studioHdr.numskinfamilies() )
- nActualSkin = 0;
-
- CBaseEntity *pBreakable = NULL;
-
-#ifdef GAME_DLL
- if ( GetGibManager() == NULL || GetGibManager()->AllowedToSpawnGib() )
-#endif
- {
- pBreakable = BreakModelCreateSingle( pOwnerEntity, &list[i], position, angles, objectVelocity, params.angularVelocity, nActualSkin, params );
- }
-
- if ( pBreakable )
- {
-#ifdef GAME_DLL
- if ( GetGibManager() )
- {
- GetGibManager()->AddGibToLRU( pBreakable->GetBaseAnimating() );
- }
-#endif
-
-#ifndef GAME_DLL
- if ( bBurning && cl_burninggibs.GetBool() )
- {
- pBreakable->ParticleProp()->Create( "burninggibs", PATTACH_POINT_FOLLOW, "bloodpoint" );
- }
-#endif
- if ( pOwnerEntity && pOwnerEntity->IsEffectActive( EF_NOSHADOW ) )
- {
- pBreakable->AddEffects( EF_NOSHADOW );
- }
-
- // If burst scale is set, this piece should 'burst' away from
- // the origin in addition to travelling in the wished velocity.
- if ( list[i].burstScale != 0.0 )
- {
- Vector vecBurstDir = position - params.origin;
-
- // If $autocenter wasn't used, try the center of the piece
- if ( vecBurstDir == vec3_origin )
- {
- vecBurstDir = pBreakable->WorldSpaceCenter() - params.origin;
- }
-
- VectorNormalize( vecBurstDir );
-
- pBreakable->ApplyAbsVelocityImpulse( vecBurstDir * list[i].burstScale );
- }
-
- // If this piece is supposed to be motion disabled, disable it
- if ( list[i].isMotionDisabled )
- {
- IPhysicsObject *pPhysicsObject = pBreakable->VPhysicsGetObject();
- if ( pPhysicsObject != NULL )
- {
- pPhysicsObject->EnableMotion( false );
- }
- }
-
- if ( !pFirstBreakable )
- {
- pFirstBreakable = pBreakable;
- }
-
- if ( pGibList )
- {
- pGibList->AddToTail( pBreakable );
- }
- }
- }
- }
- // Then see if the propdata specifies any breakable pieces
- else if ( pEntity )
- {
- IBreakableWithPropData *pBreakableInterface = dynamic_cast<IBreakableWithPropData*>(pEntity);
- if ( pBreakableInterface && pBreakableInterface->GetBreakableModel() != NULL_STRING && pBreakableInterface->GetBreakableCount() )
- {
- breakmodel_t breakModel;
-
- for ( int i = 0; i < pBreakableInterface->GetBreakableCount(); i++ )
- {
- if ( ( iPrecomputedBreakableCount != -1 ) && ( i >= iPrecomputedBreakableCount ) )
- break;
-
- Q_strncpy( breakModel.modelName, g_PropDataSystem.GetRandomChunkModel(STRING(pBreakableInterface->GetBreakableModel()), pBreakableInterface->GetMaxBreakableSize()), sizeof(breakModel.modelName) );
-
- breakModel.health = 1;
- breakModel.fadeTime = RandomFloat(5,10);
- breakModel.fadeMinDist = 0.0f;
- breakModel.fadeMaxDist = 0.0f;
- breakModel.burstScale = params.defBurstScale;
- breakModel.collisionGroup = COLLISION_GROUP_DEBRIS;
- breakModel.isRagdoll = false;
- breakModel.isMotionDisabled = false;
- breakModel.placementName[0] = 0;
- breakModel.placementIsBone = false;
-
- Vector vecObbSize = pEntity->CollisionProp()->OBBSize();
-
- // Find a random point on the plane of the original's two largest axis
- int smallestAxis = SmallestAxis( vecObbSize );
- Vector vecMins(0,0,0);
- Vector vecMaxs(1,1,1);
- vecMins[smallestAxis] = 0.5;
- vecMaxs[smallestAxis] = 0.5;
- pEntity->CollisionProp()->RandomPointInBounds( vecMins, vecMaxs, &breakModel.offset );
-
- // Push all chunks away from the center
- Vector vecBurstDir = breakModel.offset - params.origin;
- VectorNormalize( vecBurstDir );
- Vector vecVelocity = vecBurstDir * params.defBurstScale;
-
- QAngle vecAngles = pEntity->GetAbsAngles();
- int iSkin = pBreakableInterface->GetBreakableSkin();
-
- CBaseEntity *pBreakable = NULL;
-
-#ifdef GAME_DLL
- if ( GetGibManager() == NULL || GetGibManager()->AllowedToSpawnGib() )
-#endif
- {
- pBreakable = BreakModelCreateSingle( pOwnerEntity, &breakModel, breakModel.offset, vecAngles, vecVelocity, vec3_origin/*params.angularVelocity*/, iSkin, params );
- }
-
- if( pBreakable )
- {
-#ifdef GAME_DLL
- if ( GetGibManager() )
- {
- GetGibManager()->AddGibToLRU( pBreakable->GetBaseAnimating() );
- }
-#endif
- Vector vecBreakableObbSize = pBreakable->CollisionProp()->OBBSize();
-
- // Try to align the gibs along the original axis
- matrix3x4_t matrix;
- AngleMatrix( vecAngles, matrix );
- AlignBoxes( &matrix, vecObbSize, vecBreakableObbSize );
- MatrixAngles( matrix, vecAngles );
-
- if ( pBreakable->VPhysicsGetObject() )
- {
- Vector pos;
- pBreakable->VPhysicsGetObject()->GetPosition( &pos, NULL );
- pBreakable->VPhysicsGetObject()->SetPosition( pos, vecAngles, true );
- }
-
- pBreakable->SetAbsAngles( vecAngles );
-
- if ( pOwnerEntity->IsEffectActive( EF_NOSHADOW ) )
- {
- pBreakable->AddEffects( EF_NOSHADOW );
- }
-
- if ( !pFirstBreakable )
- {
- pFirstBreakable = pBreakable;
- }
-
- if ( pGibList )
- {
- pGibList->AddToTail( pBreakable );
- }
- }
- else
- {
- DevWarning( "PropBreakableCreateAll: Could not create model %s\n", breakModel.modelName );
- }
- }
- }
- }
-
- return pFirstBreakable;
-}
-
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: static_prop - don't move, don't animate, don't do anything.
+// physics_prop - move, take damage, but don't animate
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "props_shared.h"
+#include "filesystem.h"
+#include "animation.h"
+#include <vcollide_parse.h>
+#include <bone_setup.h>
+#include <tier0/vprof.h>
+
+#ifdef CLIENT_DLL
+#include "gamestringpool.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+ConVar sv_pushaway_clientside_size( "sv_pushaway_clientside_size", "15", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Minimum size of pushback objects" );
+ConVar props_break_max_pieces( "props_break_max_pieces", "-1", 0, "Maximum prop breakable piece count (-1 = model default)" );
+ConVar props_break_max_pieces_perframe( "props_break_max_pieces_perframe", "-1", FCVAR_REPLICATED, "Maximum prop breakable piece count per frame (-1 = model default)" );
+#ifdef GAME_DLL
+extern ConVar breakable_multiplayer;
+#else
+ConVar cl_burninggibs( "cl_burninggibs", "0", 0, "A burning player that gibs has burning gibs." );
+#endif // GAME_DLL
+
+extern bool PropBreakableCapEdictsOnCreateAll(int modelindex, IPhysicsObject *pPhysics, const breakablepropparams_t &params, CBaseEntity *pEntity, int iPrecomputedBreakableCount = -1 );
+extern CBaseEntity *BreakModelCreateSingle( CBaseEntity *pOwner, breakmodel_t *pModel, const Vector &position,
+ const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, int nSkin, const breakablepropparams_t &params );
+
+static int nPropBreakablesPerFrameCount = 0;
+static int nFrameNumber = 0;
+
+//=============================================================================================================
+// UTILITY FUNCS
+//=============================================================================================================
+//-----------------------------------------------------------------------------
+// Purpose: returns the axis index with the greatest size
+// Input : &vec -
+// Output : static int
+//-----------------------------------------------------------------------------
+static int GreatestAxis( const Vector &vec )
+{
+ if ( vec.x > vec.y )
+ {
+ if ( vec.x > vec.z )
+ return 0;
+ return 2;
+ }
+ if ( vec.y > vec.z )
+ return 1;
+ return 2;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: returns the axis index with the smallest size
+// Input : &vec -
+// Output : static int
+//-----------------------------------------------------------------------------
+static int SmallestAxis( const Vector &vec )
+{
+ if ( vec.x < vec.y )
+ {
+ if ( vec.x < vec.z )
+ return 0;
+ return 2;
+ }
+ if ( vec.y < vec.z )
+ return 1;
+ return 2;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Rotates a matrix by 90 degrees in the plane of axis0/axis1
+// Input : &matrix -
+// axis0 -
+// axis1 -
+// Output : static void
+//-----------------------------------------------------------------------------
+static void MatrixRot90( matrix3x4_t &matrix, int axis0, int axis1 )
+{
+ Vector col0, col1;
+ MatrixGetColumn( matrix, axis0, col0 );
+ MatrixGetColumn( matrix, axis1, col1 );
+ MatrixSetColumn( col1, axis0, matrix );
+ MatrixSetColumn( -col0, axis1, matrix );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Given two symmetric boxes, rotate the coordinate frame by the necessary
+// 90 degree rotations to approximately align them
+// Input : *pInOutMatrix -
+// &boxExtents1 -
+// &boxExtents2 -
+//-----------------------------------------------------------------------------
+static void AlignBoxes( matrix3x4_t *pInOutMatrix, const Vector &boxExtents1, const Vector &boxExtents2 )
+{
+ int rotCount = 0;
+ struct
+ {
+ int axis0;
+ int axis1;
+ } rotations[2];
+ Vector ext1 = boxExtents1;
+ Vector ext2 = boxExtents2;
+
+ int axis0 = GreatestAxis( ext1 );
+ int axis1 = GreatestAxis( ext2 );
+ if ( axis0 != axis1 )
+ {
+ rotations[rotCount].axis0 = axis0;
+ rotations[rotCount].axis1 = axis1;
+ rotCount++;
+ ext2[axis1] = ext2[axis0];
+ }
+ ext1[axis0] = 0;
+ ext2[axis0] = 0;
+
+ axis0 = GreatestAxis(ext1);
+ axis1 = GreatestAxis(ext2);
+ if ( axis0 != axis1 )
+ {
+ rotations[rotCount].axis0 = axis0;
+ rotations[rotCount].axis1 = axis1;
+ rotCount++;
+ }
+
+ while ( rotCount > 0 )
+ {
+ rotCount--;
+ MatrixRot90( *pInOutMatrix, rotations[rotCount].axis0, rotations[rotCount].axis1 );
+ }
+}
+
+//=============================================================================================================
+// PROP DATA
+//=============================================================================================================
+CPropData g_PropDataSystem;
+
+// Parsing details for each of the propdata interactions
+struct propdata_interaction_s
+{
+ const char *pszSectionName;
+ const char *pszKeyName;
+ const char *pszValue;
+};
+
+#if !defined(_STATIC_LINKED) || defined(CLIENT_DLL)
+propdata_interaction_s sPropdataInteractionSections[PROPINTER_NUM_INTERACTIONS] =
+{
+ { "physgun_interactions", "onworldimpact", "stick" }, // PROPINTER_PHYSGUN_WORLD_STICK,
+ { "physgun_interactions", "onfirstimpact", "break" }, // PROPINTER_PHYSGUN_FIRST_BREAK,
+ { "physgun_interactions", "onfirstimpact", "paintsplat" }, // PROPINTER_PHYSGUN_FIRST_PAINT,
+ { "physgun_interactions", "onfirstimpact", "impale" }, // PROPINTER_PHYSGUN_FIRST_IMPALE,
+ { "physgun_interactions", "onlaunch", "spin_none" }, // PROPINTER_PHYSGUN_LAUNCH_SPIN_NONE,
+ { "physgun_interactions", "onlaunch", "spin_zaxis" }, // PROPINTER_PHYSGUN_LAUNCH_SPIN_Z,
+ { "physgun_interactions", "onbreak", "explode_fire" }, // PROPINTER_PHYSGUN_BREAK_EXPLODE,
+ { "physgun_interactions", "damage", "none" }, // PROPINTER_PHYSGUN_DAMAGE_NONE,
+
+ { "fire_interactions", "flammable", "yes" }, // PROPINTER_FIRE_FLAMMABLE,
+ { "fire_interactions", "explosive_resist", "yes" }, // PROPINTER_FIRE_EXPLOSIVE_RESIST,
+ { "fire_interactions", "ignite", "halfhealth" }, // PROPINTER_FIRE_IGNITE_HALFHEALTH,
+
+ { "physgun_interactions", "onpickup", "create_flare" }, // PROPINTER_PHYSGUN_CREATE_FLARE,
+
+ { "physgun_interactions", "allow_overhead", "yes" }, // PROPINTER_PHYSGUN_ALLOW_OVERHEAD,
+
+ { "world_interactions", "onworldimpact", "bloodsplat" }, // PROPINTER_WORLD_BLOODSPLAT,
+};
+#else
+extern propdata_interaction_s sPropdataInteractionSections[PROPINTER_NUM_INTERACTIONS];
+#endif
+
+//-----------------------------------------------------------------------------
+// Constructor, destructor
+//-----------------------------------------------------------------------------
+CPropData::CPropData( void ) :
+ CAutoGameSystem( "CPropData" )
+{
+ m_bPropDataLoaded = false;
+ m_pKVPropData = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Inherited from IAutoServerSystem
+//-----------------------------------------------------------------------------
+void CPropData::LevelInitPreEntity( void )
+{
+ m_BreakableChunks.RemoveAll();
+ ParsePropDataFile();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropData::LevelShutdownPostEntity( void )
+{
+ if ( m_pKVPropData )
+ {
+ m_pKVPropData->deleteThis();
+ m_pKVPropData = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Clear out the stats + their history
+//-----------------------------------------------------------------------------
+void CPropData::ParsePropDataFile( void )
+{
+ m_pKVPropData = new KeyValues( "PropDatafile" );
+ if ( !m_pKVPropData->LoadFromFile( filesystem, "scripts/propdata.txt" ) )
+ {
+ m_pKVPropData->deleteThis();
+ m_pKVPropData = NULL;
+ return;
+ }
+
+ m_bPropDataLoaded = true;
+
+ // Now try and parse out the breakable section
+ KeyValues *pBreakableSection = m_pKVPropData->FindKey( "BreakableModels" );
+ if ( pBreakableSection )
+ {
+ KeyValues *pChunkSection = pBreakableSection->GetFirstSubKey();
+ while ( pChunkSection )
+ {
+ // Create a new chunk section and add it to our list
+ int index = m_BreakableChunks.AddToTail();
+ propdata_breakablechunk_t *pBreakableChunk = &m_BreakableChunks[index];
+ pBreakableChunk->iszChunkType = AllocPooledString( pChunkSection->GetName() );
+
+ // Read in all the model names
+ KeyValues *pModelName = pChunkSection->GetFirstSubKey();
+ while ( pModelName )
+ {
+ const char *pModel = pModelName->GetName();
+ string_t pooledName = AllocPooledString( pModel );
+ pBreakableChunk->iszChunkModels.AddToTail( pooledName );
+ CBaseEntity::PrecacheModel( STRING( pooledName ) );
+
+ pModelName = pModelName->GetNextKey();
+ }
+
+ pChunkSection = pChunkSection->GetNextKey();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Parse a keyvalues section into the prop
+//
+// pInteractionSection is a bit of jiggery-pokery to get around the unfortunate
+// fact that the interaction KV sections ("physgun_interactions", "fire_interactions", etc)
+// are OUTSIDE the "prop_data" KV section in the model, but may be contained WITHIN the
+// specified Base's "prop_data" section (i.e. in propdata.txt)
+//-----------------------------------------------------------------------------
+int CPropData::ParsePropFromKV( CBaseEntity *pProp, KeyValues *pSection, KeyValues *pInteractionSection )
+{
+ IBreakableWithPropData *pBreakableInterface = dynamic_cast<IBreakableWithPropData*>(pProp);
+ if ( !pBreakableInterface )
+ return PARSE_FAILED_BAD_DATA;
+
+ if ( !pBreakableInterface )
+ return PARSE_FAILED_BAD_DATA;
+
+ int iBaseResult = PARSE_SUCCEEDED;
+
+ // Do we have a base?
+ char const *pszBase = pSection->GetString( "base" );
+ if ( pszBase && pszBase[0] )
+ {
+ iBaseResult = ParsePropFromBase( pProp, pszBase );
+ if ( (iBaseResult != PARSE_SUCCEEDED) && (iBaseResult != PARSE_SUCCEEDED_ALLOWED_STATIC) )
+ return iBaseResult;
+ }
+
+ // Allow overriding of Block LOS
+ int iBlockLOS = pSection->GetFloat( "blockLOS", -1 );
+ if ( iBlockLOS != -1 )
+ {
+ pBreakableInterface->SetPropDataBlocksLOS( iBlockLOS != 0 );
+ }
+
+ // Set whether AI can walk on this prop
+ int iIsWalkable = pSection->GetFloat( "AIWalkable", -1 );
+ if ( iIsWalkable != -1 )
+ {
+ pBreakableInterface->SetPropDataIsAIWalkable( iIsWalkable != 0 );
+ }
+
+ // Set custom damage table
+ const char *pszTableName;
+ if ( pBreakableInterface->GetPhysicsDamageTable() == NULL_STRING )
+ {
+ pszTableName = pSection->GetString( "damage_table", NULL );
+ }
+ else
+ {
+ pszTableName = pSection->GetString( "damage_table", STRING(pBreakableInterface->GetPhysicsDamageTable()) );
+ }
+ if ( pszTableName && pszTableName[0] )
+ {
+ pBreakableInterface->SetPhysicsDamageTable( AllocPooledString( pszTableName ) );
+ }
+ else
+ {
+ pBreakableInterface->SetPhysicsDamageTable( NULL_STRING );
+ }
+
+ // Get multiplayer physics mode if not set by map
+ pBreakableInterface->SetPhysicsMode( pSection->GetInt( "physicsmode",
+ pBreakableInterface->GetPhysicsMode() ) );
+
+ const char *multiplayer_break = pSection->GetString( "multiplayer_break", NULL );
+ if ( multiplayer_break )
+ {
+ mp_break_t mode = MULTIPLAYER_BREAK_DEFAULT;
+ if ( FStrEq( multiplayer_break, "server" ) )
+ {
+ mode = MULTIPLAYER_BREAK_SERVERSIDE;
+ }
+ else if ( FStrEq( multiplayer_break, "client" ) )
+ {
+ mode = MULTIPLAYER_BREAK_CLIENTSIDE;
+ }
+ else if ( FStrEq( multiplayer_break, "both" ) )
+ {
+ mode = MULTIPLAYER_BREAK_BOTH;
+ }
+ pBreakableInterface->SetMultiplayerBreakMode( mode );
+ }
+
+ // Get damage modifiers, but only if they're specified, because our base may have already overridden them.
+ pBreakableInterface->SetDmgModBullet( pSection->GetFloat( "dmg.bullets", pBreakableInterface->GetDmgModBullet() ) );
+ pBreakableInterface->SetDmgModClub( pSection->GetFloat( "dmg.club", pBreakableInterface->GetDmgModClub() ) );
+ pBreakableInterface->SetDmgModExplosive( pSection->GetFloat( "dmg.explosive", pBreakableInterface->GetDmgModExplosive() ) );
+
+ // Get the health (unless this is an override prop)
+ if ( !FClassnameIs( pProp, "prop_physics_override" ) && !FClassnameIs( pProp, "prop_dynamic_override" ) )
+ {
+ pProp->SetHealth( pSection->GetInt( "health", pProp->GetHealth() ) );
+
+ // Explosive?
+ pBreakableInterface->SetExplosiveDamage( pSection->GetFloat( "explosive_damage", pBreakableInterface->GetExplosiveDamage() ) );
+ pBreakableInterface->SetExplosiveRadius( pSection->GetFloat( "explosive_radius", pBreakableInterface->GetExplosiveRadius() ) );
+
+#ifdef GAME_DLL
+ // If we now have health, we're not allowed to ignore physics damage
+ if ( pProp->GetHealth() )
+ {
+ pProp->RemoveSpawnFlags( SF_PHYSPROP_DONT_TAKE_PHYSICS_DAMAGE );
+ }
+#endif
+ }
+
+ const char *pszBreakableModel;
+ if ( pBreakableInterface->GetBreakableModel() == NULL_STRING )
+ {
+ pszBreakableModel = pSection->GetString( "breakable_model", NULL );
+ }
+ else
+ {
+ pszBreakableModel = pSection->GetString( "breakable_model", STRING(pBreakableInterface->GetBreakableModel()) );
+ }
+ if ( pszBreakableModel && pszBreakableModel[0] )
+ {
+ pBreakableInterface->SetBreakableModel( AllocPooledString( pszBreakableModel ) );
+ }
+ else
+ {
+ pBreakableInterface->SetBreakableModel( NULL_STRING );
+ }
+ pBreakableInterface->SetBreakableSkin( pSection->GetInt( "breakable_skin", pBreakableInterface->GetBreakableSkin() ) );
+ pBreakableInterface->SetBreakableCount( pSection->GetInt( "breakable_count", pBreakableInterface->GetBreakableCount() ) );
+
+ // Calculate the maximum size of the breakables this breakable will produce
+ Vector vecSize = pProp->CollisionProp()->OBBSize();
+ // Throw away the smallest coord
+ int iSmallest = SmallestAxis(vecSize);
+ vecSize[iSmallest] = 1;
+ float flVolume = vecSize.x * vecSize.y * vecSize.z;
+ int iMaxSize = floor( flVolume / (32.0*32.0) );
+ pBreakableInterface->SetMaxBreakableSize( iMaxSize );
+
+ // Now parse our interactions
+ for ( int i = 0; i < PROPINTER_NUM_INTERACTIONS; i++ )
+ {
+ // If we hit this assert, we have too many interactions for our current storage solution to handle
+ Assert( i < 32 );
+
+ propdata_interaction_s *pInteraction = &sPropdataInteractionSections[i];
+
+ KeyValues *pkvCurrentInter = pInteractionSection->FindKey( pInteraction->pszSectionName );
+ if ( pkvCurrentInter )
+ {
+ char const *pszInterBase = pkvCurrentInter->GetString( pInteraction->pszKeyName );
+ if ( pszInterBase && pszInterBase[0] && !stricmp( pszInterBase, pInteraction->pszValue ) )
+ {
+ pBreakableInterface->SetInteraction( (propdata_interactions_t)i );
+ }
+ }
+ }
+
+ // If the base said we're allowed to be static, return that
+ if ( iBaseResult == PARSE_SUCCEEDED_ALLOWED_STATIC )
+ return PARSE_SUCCEEDED_ALLOWED_STATIC;
+
+ // Otherwise, see if our propdata says we are allowed to be static
+ if ( pSection->GetInt( "allowstatic", 0 ) )
+ return PARSE_SUCCEEDED_ALLOWED_STATIC;
+
+ return PARSE_SUCCEEDED;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Fill out a prop's with base data parsed from the propdata file
+//-----------------------------------------------------------------------------
+int CPropData::ParsePropFromBase( CBaseEntity *pProp, const char *pszPropData )
+{
+ if ( !m_bPropDataLoaded )
+ return PARSE_FAILED_NO_DATA;
+
+ IBreakableWithPropData *pBreakableInterface = dynamic_cast<IBreakableWithPropData*>(pProp);
+
+ if ( !pBreakableInterface )
+ {
+ return PARSE_FAILED_BAD_DATA;
+ }
+
+ if ( !m_pKVPropData )
+ {
+ return PARSE_FAILED_BAD_DATA;
+ }
+
+ // Find the specified propdata
+ KeyValues *pSection = m_pKVPropData->FindKey( pszPropData );
+ if ( !pSection )
+ {
+ 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 );
+ return PARSE_FAILED_BAD_DATA;
+ }
+
+ // Store off the first base data for debugging
+ if ( pBreakableInterface->GetBasePropData() == NULL_STRING )
+ {
+ pBreakableInterface->SetBasePropData( AllocPooledString( pszPropData ) );
+ }
+
+ return ParsePropFromKV( pProp, pSection, pSection );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CPropData::GetRandomChunkModel( const char *pszBreakableSection, int iMaxSize )
+{
+ if ( !m_bPropDataLoaded )
+ return NULL;
+
+ // Find the right section
+ int iCount = m_BreakableChunks.Count();
+ int i;
+ for ( i = 0; i < iCount; i++ )
+ {
+ if ( !Q_strncmp( STRING(m_BreakableChunks[i].iszChunkType), pszBreakableSection, strlen(pszBreakableSection) ) )
+ break;
+ }
+ if ( i == iCount )
+ return NULL;
+
+ // Now pick a random one and return it
+ int iRandom;
+ if ( iMaxSize == -1 )
+ {
+ iRandom = RandomInt( 0, m_BreakableChunks[i].iszChunkModels.Count()-1 );
+ }
+ else
+ {
+ // Don't pick anything over the specified size
+ iRandom = RandomInt( 0, MIN(iMaxSize, m_BreakableChunks[i].iszChunkModels.Count()-1) );
+ }
+
+ return STRING(m_BreakableChunks[i].iszChunkModels[iRandom]);
+}
+
+
+// ensure that a model name from a qc file is properly formatted
+static const char *FixupModelName( char *pOut, int sizeOut, const char *pModelNameIn )
+{
+ char tmp[1024];
+
+ Q_strncpy( tmp, pModelNameIn, sizeof(tmp) );
+ if ( Q_strnicmp( tmp, "models/", 7 ) )
+ {
+ Q_snprintf( pOut, sizeOut, "models/%s" , tmp );
+ }
+ else
+ {
+ Q_strncpy( pOut, tmp, sizeOut);
+ }
+ int len = Q_strlen(pOut);
+ if ( len < 4 || Q_stricmp( pOut + (len-4), ".mdl" ) )
+ {
+ Q_strncat( pOut, ".mdl", sizeOut, COPY_ALL_CHARACTERS );
+ }
+
+ return pOut;
+}
+
+
+//-----------------------------------------------------------------------------
+// breakable prop functions
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+// list of models to break into
+class CBreakParser : public IVPhysicsKeyHandler
+{
+public:
+ CBreakParser( float defaultBurstScale, int defaultCollisionGroup )
+ : m_defaultBurstScale(defaultBurstScale), m_defaultCollisionGroup(defaultCollisionGroup) {}
+
+ void ParseModelName( breakmodel_t *pModel, const char *pValue )
+ {
+ FixupModelName( pModel->modelName, sizeof(pModel->modelName), pValue );
+ }
+ virtual void ParseKeyValue( void *pData, const char *pKey, const char *pValue )
+ {
+ breakmodel_t *pModel = (breakmodel_t *)pData;
+ if ( !strcmpi( pKey, "model" ) )
+ {
+ ParseModelName( pModel, pValue );
+ }
+ else if (!strcmpi( pKey, "ragdoll" ) )
+ {
+ ParseModelName( pModel, pValue );
+ pModel->isRagdoll = true;
+ }
+ else if (!strcmpi( pKey, "motiondisabled" ) )
+ {
+ pModel->isMotionDisabled = true;
+ }
+ else if ( !strcmpi( pKey, "offset" ) )
+ {
+ UTIL_StringToVector( pModel->offset.Base(), pValue );
+ }
+ else if ( !strcmpi( pKey, "health" ) )
+ {
+ pModel->health = atof(pValue);
+ }
+ else if ( !strcmpi( pKey, "fadetime" ) )
+ {
+ pModel->fadeTime = atof(pValue);
+ if ( !m_wroteCollisionGroup )
+ {
+ pModel->collisionGroup = COLLISION_GROUP_DEBRIS;
+ }
+ }
+ else if ( !strcmpi( pKey, "fademindist" ) )
+ {
+ pModel->fadeMinDist = atof(pValue);
+ }
+ else if ( !strcmpi( pKey, "fademaxdist" ) )
+ {
+ pModel->fadeMaxDist = atof(pValue);
+ }
+ else if ( !strcmpi( pKey, "debris" ) )
+ {
+ pModel->collisionGroup = atoi(pValue) > 0 ? COLLISION_GROUP_DEBRIS : COLLISION_GROUP_INTERACTIVE;
+ m_wroteCollisionGroup = true;
+ }
+ else if ( !strcmpi( pKey, "burst" ) )
+ {
+ pModel->burstScale = atof( pValue );
+ }
+ else if ( !strcmpi( pKey, "placementbone" ) )
+ {
+ Q_strncpy( pModel->placementName, pValue, sizeof(pModel->placementName) );
+ pModel->placementIsBone = true;
+ }
+ else if ( !strcmpi( pKey, "placementattachment" ) )
+ {
+ Q_strncpy( pModel->placementName, pValue, sizeof(pModel->placementName) );
+ pModel->placementIsBone = false;
+ }
+ else if ( !strcmpi( pKey, "multiplayer_break" ) )
+ {
+ if ( FStrEq( pValue, "server" ) )
+ {
+ pModel->mpBreakMode = MULTIPLAYER_BREAK_SERVERSIDE;
+ }
+ else if ( FStrEq( pValue, "client" ) )
+ {
+ pModel->mpBreakMode = MULTIPLAYER_BREAK_CLIENTSIDE;
+ }
+ }
+ }
+ virtual void SetDefaults( void *pData )
+ {
+ breakmodel_t *pModel = (breakmodel_t *)pData;
+ pModel->modelName[0] = 0;
+ pModel->offset = vec3_origin;
+ pModel->health = 1;
+ pModel->fadeTime = 20.0f;
+ pModel->fadeMinDist = 0.0f;
+ pModel->fadeMaxDist = 0.0f;
+ pModel->burstScale = m_defaultBurstScale;
+ pModel->collisionGroup = m_defaultCollisionGroup;
+ pModel->isRagdoll = false;
+ pModel->isMotionDisabled = false;
+ pModel->placementName[0] = 0;
+ pModel->placementIsBone = false;
+ pModel->mpBreakMode = MULTIPLAYER_BREAK_DEFAULT;
+ m_wroteCollisionGroup = false;
+ }
+
+private:
+ int m_defaultCollisionGroup;
+ float m_defaultBurstScale;
+ bool m_wroteCollisionGroup;
+};
+
+void BreakModelList( CUtlVector<breakmodel_t> &list, int modelindex, float defBurstScale, int defCollisionGroup )
+{
+ vcollide_t *pCollide = modelinfo->GetVCollide( modelindex );
+ if ( !pCollide )
+ return;
+
+ IVPhysicsKeyParser *pParse = physcollision->VPhysicsKeyParserCreate( pCollide->pKeyValues );
+ while ( !pParse->Finished() )
+ {
+ CBreakParser breakParser( defBurstScale, defCollisionGroup );
+
+ const char *pBlock = pParse->GetCurrentBlockName();
+ if ( !strcmpi( pBlock, "break" ) )
+ {
+ int index = list.AddToTail();
+ breakmodel_t &breakModel = list[index];
+ pParse->ParseCustom( &breakModel, &breakParser );
+ }
+ else
+ {
+ pParse->SkipBlock();
+ }
+ }
+ physcollision->VPhysicsKeyParserDestroy( pParse );
+}
+
+#if !defined(_STATIC_LINKED) || defined(CLIENT_DLL)
+int GetAutoMultiplayerPhysicsMode( Vector size, float mass )
+{
+ float volume = size.x * size.y * size.z;
+
+ float minsize = sv_pushaway_clientside_size.GetFloat();
+
+ // if it's too small, client side only
+ if ( volume < (minsize*minsize*minsize) )
+ return PHYSICS_MULTIPLAYER_CLIENTSIDE;
+
+ // if it's too light, no player pushback
+ if ( mass < 8.0 )
+ return PHYSICS_MULTIPLAYER_NON_SOLID;
+
+ // full pushbackmode
+ return PHYSICS_MULTIPLAYER_SOLID;
+}
+#else
+extern int GetAutoMultiplayerPhysicsMode( Vector size, float mass );
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns a string describing a real-world equivalent mass.
+// Input : flMass - mass in kg
+//-----------------------------------------------------------------------------
+#if !defined(_STATIC_LINKED) || defined(CLIENT_DLL)
+const char *GetMassEquivalent(float flMass)
+{
+ static struct
+ {
+ float flMass;
+ const char *sz;
+ } masstext[] =
+ {
+ { 5e-6, "snowflake" },
+ { 2.5e-3, "ping-pong ball" },
+ { 5e-3, "penny" },
+ { 0.05, "golf ball" },
+ { 0.17, "billard ball" },
+ { 2, "bag of sugar" },
+ { 7, "male cat" },
+ { 10, "bowling ball" },
+ { 30, "dog" },
+ { 60, "cheetah" },
+ { 90, "adult male human" },
+ { 250, "refrigerator" },
+ { 600, "race horse" },
+ { 1000, "small car" },
+ { 1650, "medium car" },
+ { 2500, "large car" },
+ { 6000, "t-rex" },
+ { 7200, "elephant" },
+ { 8e4, "space shuttle" },
+ { 7e5, "locomotive" },
+ { 9.2e6, "Eiffel tower" },
+ { 6e24, "the Earth" },
+ { 7e24, "really freaking heavy" },
+ };
+
+ for (int i = 0; i < sizeof(masstext) / sizeof(masstext[0]) - 1; i++)
+ {
+ if (flMass < masstext[i].flMass)
+ {
+ return masstext[i].sz;
+ }
+ }
+
+ return masstext[ sizeof(masstext) / sizeof(masstext[0]) - 1 ].sz;
+}
+#else
+extern const char *GetMassEquivalent(float flMass);
+#endif
+
+#ifdef GAME_DLL
+//=========================================================
+//=========================================================
+class CGameGibManager : public CBaseEntity
+{
+ DECLARE_CLASS( CGameGibManager, CBaseEntity );
+ DECLARE_DATADESC();
+
+public:
+
+ CGameGibManager() : m_iCurrentMaxPieces(-1), m_iMaxPieces(-1), m_iMaxPiecesDX8(-1) {}
+
+ void Activate( void );
+ void AddGibToLRU( CBaseAnimating *pEntity );
+
+ inline bool AllowedToSpawnGib( void );
+
+private:
+
+ void UpdateMaxPieces();
+
+ void InputSetMaxPieces( inputdata_t &inputdata );
+ void InputSetMaxPiecesDX8( inputdata_t &inputdata );
+
+ typedef CHandle<CBaseAnimating> CGibHandle;
+ CUtlLinkedList< CGibHandle > m_LRU;
+
+ bool m_bAllowNewGibs;
+
+ int m_iDXLevel;
+ int m_iCurrentMaxPieces;
+ int m_iMaxPieces;
+ int m_iMaxPiecesDX8;
+ int m_iLastFrame;
+};
+
+BEGIN_DATADESC( CGameGibManager )
+ // Silence perfidous classcheck!
+ //DEFINE_FIELD( m_iCurrentMaxPieces, FIELD_INTEGER ),
+ //DEFINE_FIELD( m_iLastFrame, FIELD_INTEGER ),
+ //DEFINE_FIELD( m_iDXLevel, FIELD_INTEGER ),
+ DEFINE_KEYFIELD( m_iMaxPieces, FIELD_INTEGER, "maxpieces" ),
+ DEFINE_KEYFIELD( m_iMaxPiecesDX8, FIELD_INTEGER, "maxpiecesdx8" ),
+ DEFINE_KEYFIELD( m_bAllowNewGibs, FIELD_BOOLEAN, "allownewgibs" ),
+
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMaxPieces", InputSetMaxPieces ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMaxPiecesDX8", InputSetMaxPiecesDX8 ),
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( game_gib_manager, CGameGibManager );
+
+
+void CGameGibManager::Activate( void )
+{
+ m_LRU.Purge();
+
+ // Cache off the DX level for use later.
+ ConVarRef mat_dxlevel( "mat_dxlevel" );
+ m_iDXLevel = mat_dxlevel.GetInt();
+
+ UpdateMaxPieces();
+
+ BaseClass::Activate();
+}
+
+void CGameGibManager::UpdateMaxPieces()
+{
+ // If we're running DX8, use the DX8 gib limit if set.
+ if ( ( m_iDXLevel < 90 ) && ( m_iMaxPiecesDX8 >= 0 ) )
+ {
+ m_iCurrentMaxPieces = m_iMaxPiecesDX8;
+ }
+ else
+ {
+ m_iCurrentMaxPieces = m_iMaxPieces;
+ }
+}
+
+
+bool CGameGibManager::AllowedToSpawnGib( void )
+{
+ if ( m_bAllowNewGibs )
+ return true;
+
+ // We're not tracking gibs at the moment
+ if ( m_iCurrentMaxPieces < 0 )
+ return true;
+
+ if ( m_iCurrentMaxPieces == 0 )
+ return false;
+
+ if ( m_iLastFrame == gpGlobals->framecount )
+ {
+ if ( m_LRU.Count() >= m_iCurrentMaxPieces )
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void CGameGibManager::InputSetMaxPieces( inputdata_t &inputdata )
+{
+ m_iMaxPieces = inputdata.value.Int();
+ UpdateMaxPieces();
+}
+
+void CGameGibManager::InputSetMaxPiecesDX8( inputdata_t &inputdata )
+{
+ m_iMaxPiecesDX8 = inputdata.value.Int();
+ UpdateMaxPieces();
+}
+
+void CGameGibManager::AddGibToLRU( CBaseAnimating *pEntity )
+{
+ int i, next;
+
+ if ( pEntity == NULL )
+ return;
+
+ //Find stale gibs.
+ for ( i = m_LRU.Head(); i < m_LRU.InvalidIndex(); i = next )
+ {
+ next = m_LRU.Next(i);
+
+ if ( m_LRU[i].Get() == NULL )
+ {
+ m_LRU.Remove(i);
+ }
+ }
+
+ // We're not tracking gibs at the moment
+ if ( m_iCurrentMaxPieces <= 0 )
+ return;
+
+ while ( m_LRU.Count() >= m_iCurrentMaxPieces )
+ {
+ i = m_LRU.Head();
+
+ //TODO: Make this fade out instead of pop.
+ UTIL_Remove( m_LRU[i] );
+ m_LRU.Remove(i);
+ }
+
+ m_LRU.AddToTail( pEntity );
+ m_iLastFrame = gpGlobals->framecount;
+}
+
+EHANDLE g_hGameGibManager;
+
+CGameGibManager *GetGibManager( void )
+{
+#ifndef HL2_EPISODIC
+ return NULL;
+#endif
+
+ if ( g_hGameGibManager == NULL )
+ {
+ g_hGameGibManager = (CGameGibManager *)gEntList.FindEntityByClassname( NULL, "game_gib_manager" );
+ }
+
+ return (CGameGibManager *)g_hGameGibManager.Get();
+}
+
+#endif
+
+void PropBreakableCreateAll( int modelindex, IPhysicsObject *pPhysics, const breakablepropparams_t &params, CBaseEntity *pEntity, int iPrecomputedBreakableCount, bool bIgnoreGibLimit, bool defaultLocation )
+{
+ // Check for prop breakable count reset.
+ int nPropCount = props_break_max_pieces_perframe.GetInt();
+ if ( nPropCount != -1 )
+ {
+ if ( nFrameNumber != gpGlobals->framecount )
+ {
+ nPropBreakablesPerFrameCount = 0;
+ nFrameNumber = gpGlobals->framecount;
+ }
+
+ // Check for max breakable count for the frame.
+ if ( nPropBreakablesPerFrameCount >= nPropCount )
+ return;
+ }
+
+ int iMaxBreakCount = bIgnoreGibLimit ? -1 : props_break_max_pieces.GetInt();
+ if ( iMaxBreakCount != -1 )
+ {
+ if ( iPrecomputedBreakableCount != -1 )
+ {
+ iPrecomputedBreakableCount = MIN( iMaxBreakCount, iPrecomputedBreakableCount );
+ }
+ else
+ {
+ iPrecomputedBreakableCount = iMaxBreakCount;
+ }
+ }
+
+#ifdef GAME_DLL
+ // On server limit break model creation
+ if ( !PropBreakableCapEdictsOnCreateAll(modelindex, pPhysics, params, pEntity, iPrecomputedBreakableCount ) )
+ {
+ DevMsg( "Failed to create PropBreakable: would exceed MAX_EDICTS\n" );
+ return;
+ }
+#endif
+
+ vcollide_t *pCollide = modelinfo->GetVCollide( modelindex );
+ if ( !pCollide )
+ return;
+
+ int nSkin = 0;
+ CBaseEntity *pOwnerEntity = pEntity;
+ CBaseAnimating *pOwnerAnim = NULL;
+ if ( pPhysics )
+ {
+ pOwnerEntity = static_cast<CBaseEntity *>(pPhysics->GetGameData());
+ }
+ if ( pOwnerEntity )
+ {
+ pOwnerAnim = pOwnerEntity->GetBaseAnimating();
+ if ( pOwnerAnim )
+ {
+ nSkin = pOwnerAnim->m_nSkin;
+ }
+ }
+ matrix3x4_t localToWorld;
+
+ CStudioHdr studioHdr;
+ const model_t *model = modelinfo->GetModel( modelindex );
+ if ( model )
+ {
+ studioHdr.Init( modelinfo->GetStudiomodel( model ) );
+ }
+
+ Vector parentOrigin = vec3_origin;
+ int parentAttachment = Studio_FindAttachment( &studioHdr, "placementOrigin" ) + 1;
+ if ( parentAttachment > 0 )
+ {
+ GetAttachmentLocalSpace( &studioHdr, parentAttachment-1, localToWorld );
+ MatrixGetColumn( localToWorld, 3, parentOrigin );
+ }
+ else
+ {
+ AngleMatrix( vec3_angle, localToWorld );
+ }
+
+ CUtlVector<breakmodel_t> list;
+
+ BreakModelList( list, modelindex, params.defBurstScale, params.defCollisionGroup );
+
+ if ( list.Count() )
+ {
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ int modelIndex = modelinfo->GetModelIndex( list[i].modelName );
+ if ( modelIndex <= 0 )
+ continue;
+
+ // Skip multiplayer pieces that should be spawning on the other dll
+#ifdef GAME_DLL
+ if ( gpGlobals->maxClients > 1 && breakable_multiplayer.GetBool() )
+#else
+ if ( gpGlobals->maxClients > 1 )
+#endif
+ {
+#ifdef GAME_DLL
+ if ( list[i].mpBreakMode == MULTIPLAYER_BREAK_CLIENTSIDE )
+ continue;
+#else
+ if ( list[i].mpBreakMode == MULTIPLAYER_BREAK_SERVERSIDE )
+ continue;
+#endif
+
+ if ( !defaultLocation && list[i].mpBreakMode == MULTIPLAYER_BREAK_DEFAULT )
+ continue;
+ }
+
+ if ( ( nPropCount != -1 ) && ( nPropBreakablesPerFrameCount > nPropCount ) )
+ break;
+
+ if ( ( iPrecomputedBreakableCount != -1 ) && ( i >= iPrecomputedBreakableCount ) )
+ break;
+
+ matrix3x4_t matrix;
+ AngleMatrix( params.angles, params.origin, matrix );
+
+ CStudioHdr studioHdr;
+ const model_t *model = modelinfo->GetModel( modelIndex );
+ if ( model )
+ {
+ studioHdr.Init( modelinfo->GetStudiomodel( model ) );
+ }
+
+ // Increment the number of breakable props this frame.
+ ++nPropBreakablesPerFrameCount;
+
+ Vector position = vec3_origin;
+ QAngle angles = params.angles;
+ if ( pOwnerAnim && list[i].placementName[0] )
+ {
+ if ( list[i].placementIsBone )
+ {
+ int boneIndex = pOwnerAnim->LookupBone( list[i].placementName );
+ if ( boneIndex >= 0 )
+ {
+ pOwnerAnim->GetBonePosition( boneIndex, position, angles );
+ AngleMatrix( angles, position, matrix );
+ }
+ }
+ else
+ {
+ int attachmentIndex = Studio_FindAttachment( &studioHdr, list[i].placementName ) + 1;
+ if ( attachmentIndex > 0 )
+ {
+ pOwnerAnim->GetAttachment( attachmentIndex, matrix );
+ MatrixAngles( matrix, angles );
+ }
+ }
+ }
+ else
+ {
+ int placementIndex = Studio_FindAttachment( &studioHdr, "placementOrigin" ) + 1;
+ Vector placementOrigin = parentOrigin;
+ if ( placementIndex > 0 )
+ {
+ GetAttachmentLocalSpace( &studioHdr, placementIndex-1, localToWorld );
+ MatrixGetColumn( localToWorld, 3, placementOrigin );
+ placementOrigin -= parentOrigin;
+ }
+
+ VectorTransform( list[i].offset - placementOrigin, matrix, position );
+ }
+ Vector objectVelocity = params.velocity;
+
+ if (pPhysics)
+ {
+ pPhysics->GetVelocityAtPoint( position, &objectVelocity );
+ }
+
+ int nActualSkin = nSkin;
+ if ( nActualSkin > studioHdr.numskinfamilies() )
+ nActualSkin = 0;
+
+ CBaseEntity *pBreakable = NULL;
+
+#ifdef GAME_DLL
+ if ( GetGibManager() == NULL || GetGibManager()->AllowedToSpawnGib() )
+#endif
+ {
+ pBreakable = BreakModelCreateSingle( pOwnerEntity, &list[i], position, angles, objectVelocity, params.angularVelocity, nActualSkin, params );
+ }
+
+ if ( pBreakable )
+ {
+#ifdef GAME_DLL
+ if ( GetGibManager() )
+ {
+ GetGibManager()->AddGibToLRU( pBreakable->GetBaseAnimating() );
+ }
+#endif
+ if ( pOwnerEntity && pOwnerEntity->IsEffectActive( EF_NOSHADOW ) )
+ {
+ pBreakable->AddEffects( EF_NOSHADOW );
+ }
+
+ // If burst scale is set, this piece should 'burst' away from
+ // the origin in addition to travelling in the wished velocity.
+ if ( list[i].burstScale != 0.0 )
+ {
+ Vector vecBurstDir = position - params.origin;
+
+ // If $autocenter wasn't used, try the center of the piece
+ if ( vecBurstDir == vec3_origin )
+ {
+ vecBurstDir = pBreakable->WorldSpaceCenter() - params.origin;
+ }
+
+ VectorNormalize( vecBurstDir );
+
+ pBreakable->ApplyAbsVelocityImpulse( vecBurstDir * list[i].burstScale );
+ }
+
+ // If this piece is supposed to be motion disabled, disable it
+ if ( list[i].isMotionDisabled )
+ {
+ IPhysicsObject *pPhysicsObject = pBreakable->VPhysicsGetObject();
+ if ( pPhysicsObject != NULL )
+ {
+ pPhysicsObject->EnableMotion( false );
+ }
+ }
+ }
+ }
+ }
+ // Then see if the propdata specifies any breakable pieces
+ else if ( pEntity )
+ {
+ IBreakableWithPropData *pBreakableInterface = dynamic_cast<IBreakableWithPropData*>(pEntity);
+ if ( pBreakableInterface && pBreakableInterface->GetBreakableModel() != NULL_STRING && pBreakableInterface->GetBreakableCount() )
+ {
+ breakmodel_t breakModel;
+
+ for ( int i = 0; i < pBreakableInterface->GetBreakableCount(); i++ )
+ {
+ if ( ( iPrecomputedBreakableCount != -1 ) && ( i >= iPrecomputedBreakableCount ) )
+ break;
+
+ Q_strncpy( breakModel.modelName, g_PropDataSystem.GetRandomChunkModel(STRING(pBreakableInterface->GetBreakableModel()), pBreakableInterface->GetMaxBreakableSize()), sizeof(breakModel.modelName) );
+
+ breakModel.health = 1;
+ breakModel.fadeTime = RandomFloat(5,10);
+ breakModel.fadeMinDist = 0.0f;
+ breakModel.fadeMaxDist = 0.0f;
+ breakModel.burstScale = params.defBurstScale;
+ breakModel.collisionGroup = COLLISION_GROUP_DEBRIS;
+ breakModel.isRagdoll = false;
+ breakModel.isMotionDisabled = false;
+ breakModel.placementName[0] = 0;
+ breakModel.placementIsBone = false;
+
+ Vector vecObbSize = pEntity->CollisionProp()->OBBSize();
+
+ // Find a random point on the plane of the original's two largest axis
+ int smallestAxis = SmallestAxis( vecObbSize );
+ Vector vecMins(0,0,0);
+ Vector vecMaxs(1,1,1);
+ vecMins[smallestAxis] = 0.5;
+ vecMaxs[smallestAxis] = 0.5;
+ pEntity->CollisionProp()->RandomPointInBounds( vecMins, vecMaxs, &breakModel.offset );
+
+ // Push all chunks away from the center
+ Vector vecBurstDir = breakModel.offset - params.origin;
+ VectorNormalize( vecBurstDir );
+ Vector vecVelocity = vecBurstDir * params.defBurstScale;
+
+ QAngle vecAngles = pEntity->GetAbsAngles();
+ int iSkin = pBreakableInterface->GetBreakableSkin();
+
+ CBaseEntity *pBreakable = NULL;
+
+#ifdef GAME_DLL
+ if ( GetGibManager() == NULL || GetGibManager()->AllowedToSpawnGib() )
+#endif
+ {
+ pBreakable = BreakModelCreateSingle( pOwnerEntity, &breakModel, breakModel.offset, vecAngles, vecVelocity, vec3_origin/*params.angularVelocity*/, iSkin, params );
+ if ( !pBreakable )
+ {
+ DevWarning( "PropBreakableCreateAll: Could not create model %s\n", breakModel.modelName );
+ }
+ }
+
+ if ( pBreakable )
+ {
+#ifdef GAME_DLL
+ if ( GetGibManager() )
+ {
+ GetGibManager()->AddGibToLRU( pBreakable->GetBaseAnimating() );
+ }
+#endif
+ Vector vecBreakableObbSize = pBreakable->CollisionProp()->OBBSize();
+
+ // Try to align the gibs along the original axis
+ matrix3x4_t matrix;
+ AngleMatrix( vecAngles, matrix );
+ AlignBoxes( &matrix, vecObbSize, vecBreakableObbSize );
+ MatrixAngles( matrix, vecAngles );
+
+ if ( pBreakable->VPhysicsGetObject() )
+ {
+ Vector pos;
+ pBreakable->VPhysicsGetObject()->GetPosition( &pos, NULL );
+ pBreakable->VPhysicsGetObject()->SetPosition( pos, vecAngles, true );
+ }
+
+ pBreakable->SetAbsAngles( vecAngles );
+
+ if ( pOwnerEntity->IsEffectActive( EF_NOSHADOW ) )
+ {
+ pBreakable->AddEffects( EF_NOSHADOW );
+ }
+ }
+ }
+ }
+ }
+}
+
+
+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 )
+{
+ breakablepropparams_t params( origin, angles, velocity, angularVelocity );
+ params.impactEnergyScale = impactEnergyScale;
+ params.defBurstScale = defBurstScale;
+ params.defCollisionGroup = defCollisionGroup;
+ PropBreakableCreateAll( modelindex, pPhysics, params, pEntity, -1, false, defaultLocation );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : modelindex -
+//-----------------------------------------------------------------------------
+void PrecacheGibsForModel( int iModel )
+{
+ VPROF_BUDGET( "PrecacheGibsForModel", VPROF_BUDGETGROUP_PLAYER );
+ vcollide_t *pCollide = modelinfo->GetVCollide( iModel );
+ if ( !pCollide )
+ return;
+
+ // The scale and group doesn't really matter at the moment, we are just using the parser to get the model name to cache.
+ CBreakParser breakParser( 1.0, COLLISION_GROUP_NONE );
+
+ // Create a parser.
+ IVPhysicsKeyParser *pParse = physcollision->VPhysicsKeyParserCreate( pCollide->pKeyValues );
+ while ( !pParse->Finished() )
+ {
+ const char *pBlock = pParse->GetCurrentBlockName();
+ if ( !strcmpi( pBlock, "break" ) )
+ {
+ breakmodel_t breakModel;
+ pParse->ParseCustom( &breakModel, &breakParser );
+ CBaseEntity::PrecacheModel( breakModel.modelName );
+ }
+ else
+ {
+ pParse->SkipBlock();
+ }
+ }
+
+ // Destroy the parser.
+ physcollision->VPhysicsKeyParserDestroy( pParse );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &list -
+// modelindex -
+// defBurstScale -
+// defCollisionGroup -
+//-----------------------------------------------------------------------------
+void BuildGibList( CUtlVector<breakmodel_t> &list, int modelindex, float defBurstScale, int defCollisionGroup )
+{
+ BreakModelList( list, modelindex, defBurstScale, defCollisionGroup );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &list -
+// modelindex -
+// *pPhysics -
+// &params -
+// *pEntity -
+// iPrecomputedBreakableCount -
+// bIgnoreGibLImit -
+// defaultLocation -
+//-----------------------------------------------------------------------------
+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 )
+{
+ // Check for prop breakable count reset.
+ int nPropCount = props_break_max_pieces_perframe.GetInt();
+ if ( nPropCount != -1 )
+ {
+ if ( nFrameNumber != gpGlobals->framecount )
+ {
+ nPropBreakablesPerFrameCount = 0;
+ nFrameNumber = gpGlobals->framecount;
+ }
+
+ // Check for max breakable count for the frame.
+ if ( nPropBreakablesPerFrameCount >= nPropCount )
+ return NULL;
+ }
+
+ int iMaxBreakCount = bIgnoreGibLimit ? -1 : props_break_max_pieces.GetInt();
+ if ( iMaxBreakCount != -1 )
+ {
+ if ( iPrecomputedBreakableCount != -1 )
+ {
+ iPrecomputedBreakableCount = MIN( iMaxBreakCount, iPrecomputedBreakableCount );
+ }
+ else
+ {
+ iPrecomputedBreakableCount = iMaxBreakCount;
+ }
+ }
+
+#ifdef GAME_DLL
+ // On server limit break model creation
+ if ( !PropBreakableCapEdictsOnCreateAll(modelindex, pPhysics, params, pEntity, iPrecomputedBreakableCount ) )
+ {
+ DevMsg( "Failed to create PropBreakable: would exceed MAX_EDICTS\n" );
+ return NULL;
+ }
+#endif
+
+ vcollide_t *pCollide = modelinfo->GetVCollide( modelindex );
+ if ( !pCollide )
+ return NULL;
+
+ int nSkin = 0;
+ CBaseEntity *pOwnerEntity = pEntity;
+ CBaseAnimating *pOwnerAnim = NULL;
+ if ( pPhysics )
+ {
+ pOwnerEntity = static_cast<CBaseEntity *>(pPhysics->GetGameData());
+ }
+ if ( pOwnerEntity )
+ {
+ pOwnerAnim = dynamic_cast<CBaseAnimating*>(pOwnerEntity);
+ if ( pOwnerAnim )
+ {
+ nSkin = pOwnerAnim->m_nSkin;
+ }
+ }
+ matrix3x4_t localToWorld;
+
+ CStudioHdr studioHdr;
+ const model_t *model = modelinfo->GetModel( modelindex );
+ if ( model )
+ {
+ studioHdr.Init( modelinfo->GetStudiomodel( model ) );
+ }
+
+ Vector parentOrigin = vec3_origin;
+ int parentAttachment = Studio_FindAttachment( &studioHdr, "placementOrigin" ) + 1;
+ if ( parentAttachment > 0 )
+ {
+ GetAttachmentLocalSpace( &studioHdr, parentAttachment-1, localToWorld );
+ MatrixGetColumn( localToWorld, 3, parentOrigin );
+ }
+ else
+ {
+ AngleMatrix( vec3_angle, localToWorld );
+ }
+
+// CUtlVector<breakmodel_t> list;
+// BreakModelList( list, modelindex, params.defBurstScale, params.defCollisionGroup );
+
+ CBaseEntity *pFirstBreakable = NULL;
+
+ if ( list.Count() )
+ {
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ int modelIndex = modelinfo->GetModelIndex( list[i].modelName );
+ if ( modelIndex <= 0 )
+ continue;
+
+ // Skip multiplayer pieces that should be spawning on the other dll
+#ifdef GAME_DLL
+ if ( gpGlobals->maxClients > 1 && breakable_multiplayer.GetBool() )
+#else
+ if ( gpGlobals->maxClients > 1 )
+#endif
+ {
+#ifdef GAME_DLL
+ if ( list[i].mpBreakMode == MULTIPLAYER_BREAK_CLIENTSIDE )
+ continue;
+#else
+ if ( list[i].mpBreakMode == MULTIPLAYER_BREAK_SERVERSIDE )
+ continue;
+#endif
+
+ if ( !defaultLocation && list[i].mpBreakMode == MULTIPLAYER_BREAK_DEFAULT )
+ continue;
+ }
+
+ if ( ( nPropCount != -1 ) && ( nPropBreakablesPerFrameCount > nPropCount ) )
+ break;
+
+ if ( ( iPrecomputedBreakableCount != -1 ) && ( i >= iPrecomputedBreakableCount ) )
+ break;
+
+ matrix3x4_t matrix;
+ AngleMatrix( params.angles, params.origin, matrix );
+
+ CStudioHdr studioHdr;
+ const model_t *model = modelinfo->GetModel( modelIndex );
+ if ( model )
+ {
+ studioHdr.Init( modelinfo->GetStudiomodel( model ) );
+ }
+
+ // Increment the number of breakable props this frame.
+ ++nPropBreakablesPerFrameCount;
+
+ Vector position = vec3_origin;
+ QAngle angles = params.angles;
+ if ( pOwnerAnim && list[i].placementName[0] )
+ {
+ if ( list[i].placementIsBone )
+ {
+ int boneIndex = pOwnerAnim->LookupBone( list[i].placementName );
+ if ( boneIndex >= 0 )
+ {
+ pOwnerAnim->GetBonePosition( boneIndex, position, angles );
+ AngleMatrix( angles, position, matrix );
+ }
+ }
+ else
+ {
+ int attachmentIndex = Studio_FindAttachment( &studioHdr, list[i].placementName ) + 1;
+ if ( attachmentIndex > 0 )
+ {
+ pOwnerAnim->GetAttachment( attachmentIndex, matrix );
+ MatrixAngles( matrix, angles );
+ }
+ }
+ }
+ else
+ {
+ int placementIndex = Studio_FindAttachment( &studioHdr, "placementOrigin" ) + 1;
+ Vector placementOrigin = parentOrigin;
+ if ( placementIndex > 0 )
+ {
+ GetAttachmentLocalSpace( &studioHdr, placementIndex-1, localToWorld );
+ MatrixGetColumn( localToWorld, 3, placementOrigin );
+ placementOrigin -= parentOrigin;
+ }
+
+ VectorTransform( list[i].offset - placementOrigin, matrix, position );
+ }
+ Vector objectVelocity = params.velocity;
+
+ float flScale = VectorNormalize( objectVelocity );
+ objectVelocity.x += RandomFloat( -1.f, 1.0f );
+ objectVelocity.y += RandomFloat( -1.0f, 1.0f );
+ objectVelocity.z += RandomFloat( 0.0f, 1.0f );
+ VectorNormalize( objectVelocity );
+ objectVelocity *= flScale;
+
+ if (pPhysics)
+ {
+ pPhysics->GetVelocityAtPoint( position, &objectVelocity );
+ }
+
+ int nActualSkin = nSkin;
+ if ( nActualSkin > studioHdr.numskinfamilies() )
+ nActualSkin = 0;
+
+ CBaseEntity *pBreakable = NULL;
+
+#ifdef GAME_DLL
+ if ( GetGibManager() == NULL || GetGibManager()->AllowedToSpawnGib() )
+#endif
+ {
+ pBreakable = BreakModelCreateSingle( pOwnerEntity, &list[i], position, angles, objectVelocity, params.angularVelocity, nActualSkin, params );
+ }
+
+ if ( pBreakable )
+ {
+#ifdef GAME_DLL
+ if ( GetGibManager() )
+ {
+ GetGibManager()->AddGibToLRU( pBreakable->GetBaseAnimating() );
+ }
+#endif
+
+#ifndef GAME_DLL
+ if ( bBurning && cl_burninggibs.GetBool() )
+ {
+ pBreakable->ParticleProp()->Create( "burninggibs", PATTACH_POINT_FOLLOW, "bloodpoint" );
+ }
+#endif
+ if ( pOwnerEntity && pOwnerEntity->IsEffectActive( EF_NOSHADOW ) )
+ {
+ pBreakable->AddEffects( EF_NOSHADOW );
+ }
+
+ // If burst scale is set, this piece should 'burst' away from
+ // the origin in addition to travelling in the wished velocity.
+ if ( list[i].burstScale != 0.0 )
+ {
+ Vector vecBurstDir = position - params.origin;
+
+ // If $autocenter wasn't used, try the center of the piece
+ if ( vecBurstDir == vec3_origin )
+ {
+ vecBurstDir = pBreakable->WorldSpaceCenter() - params.origin;
+ }
+
+ VectorNormalize( vecBurstDir );
+
+ pBreakable->ApplyAbsVelocityImpulse( vecBurstDir * list[i].burstScale );
+ }
+
+ // If this piece is supposed to be motion disabled, disable it
+ if ( list[i].isMotionDisabled )
+ {
+ IPhysicsObject *pPhysicsObject = pBreakable->VPhysicsGetObject();
+ if ( pPhysicsObject != NULL )
+ {
+ pPhysicsObject->EnableMotion( false );
+ }
+ }
+
+ if ( !pFirstBreakable )
+ {
+ pFirstBreakable = pBreakable;
+ }
+
+ if ( pGibList )
+ {
+ pGibList->AddToTail( pBreakable );
+ }
+ }
+ }
+ }
+ // Then see if the propdata specifies any breakable pieces
+ else if ( pEntity )
+ {
+ IBreakableWithPropData *pBreakableInterface = dynamic_cast<IBreakableWithPropData*>(pEntity);
+ if ( pBreakableInterface && pBreakableInterface->GetBreakableModel() != NULL_STRING && pBreakableInterface->GetBreakableCount() )
+ {
+ breakmodel_t breakModel;
+
+ for ( int i = 0; i < pBreakableInterface->GetBreakableCount(); i++ )
+ {
+ if ( ( iPrecomputedBreakableCount != -1 ) && ( i >= iPrecomputedBreakableCount ) )
+ break;
+
+ Q_strncpy( breakModel.modelName, g_PropDataSystem.GetRandomChunkModel(STRING(pBreakableInterface->GetBreakableModel()), pBreakableInterface->GetMaxBreakableSize()), sizeof(breakModel.modelName) );
+
+ breakModel.health = 1;
+ breakModel.fadeTime = RandomFloat(5,10);
+ breakModel.fadeMinDist = 0.0f;
+ breakModel.fadeMaxDist = 0.0f;
+ breakModel.burstScale = params.defBurstScale;
+ breakModel.collisionGroup = COLLISION_GROUP_DEBRIS;
+ breakModel.isRagdoll = false;
+ breakModel.isMotionDisabled = false;
+ breakModel.placementName[0] = 0;
+ breakModel.placementIsBone = false;
+
+ Vector vecObbSize = pEntity->CollisionProp()->OBBSize();
+
+ // Find a random point on the plane of the original's two largest axis
+ int smallestAxis = SmallestAxis( vecObbSize );
+ Vector vecMins(0,0,0);
+ Vector vecMaxs(1,1,1);
+ vecMins[smallestAxis] = 0.5;
+ vecMaxs[smallestAxis] = 0.5;
+ pEntity->CollisionProp()->RandomPointInBounds( vecMins, vecMaxs, &breakModel.offset );
+
+ // Push all chunks away from the center
+ Vector vecBurstDir = breakModel.offset - params.origin;
+ VectorNormalize( vecBurstDir );
+ Vector vecVelocity = vecBurstDir * params.defBurstScale;
+
+ QAngle vecAngles = pEntity->GetAbsAngles();
+ int iSkin = pBreakableInterface->GetBreakableSkin();
+
+ CBaseEntity *pBreakable = NULL;
+
+#ifdef GAME_DLL
+ if ( GetGibManager() == NULL || GetGibManager()->AllowedToSpawnGib() )
+#endif
+ {
+ pBreakable = BreakModelCreateSingle( pOwnerEntity, &breakModel, breakModel.offset, vecAngles, vecVelocity, vec3_origin/*params.angularVelocity*/, iSkin, params );
+ }
+
+ if( pBreakable )
+ {
+#ifdef GAME_DLL
+ if ( GetGibManager() )
+ {
+ GetGibManager()->AddGibToLRU( pBreakable->GetBaseAnimating() );
+ }
+#endif
+ Vector vecBreakableObbSize = pBreakable->CollisionProp()->OBBSize();
+
+ // Try to align the gibs along the original axis
+ matrix3x4_t matrix;
+ AngleMatrix( vecAngles, matrix );
+ AlignBoxes( &matrix, vecObbSize, vecBreakableObbSize );
+ MatrixAngles( matrix, vecAngles );
+
+ if ( pBreakable->VPhysicsGetObject() )
+ {
+ Vector pos;
+ pBreakable->VPhysicsGetObject()->GetPosition( &pos, NULL );
+ pBreakable->VPhysicsGetObject()->SetPosition( pos, vecAngles, true );
+ }
+
+ pBreakable->SetAbsAngles( vecAngles );
+
+ if ( pOwnerEntity->IsEffectActive( EF_NOSHADOW ) )
+ {
+ pBreakable->AddEffects( EF_NOSHADOW );
+ }
+
+ if ( !pFirstBreakable )
+ {
+ pFirstBreakable = pBreakable;
+ }
+
+ if ( pGibList )
+ {
+ pGibList->AddToTail( pBreakable );
+ }
+ }
+ else
+ {
+ DevWarning( "PropBreakableCreateAll: Could not create model %s\n", breakModel.modelName );
+ }
+ }
+ }
+ }
+
+ return pFirstBreakable;
+}
+