From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/shared/props_shared.cpp | 3276 +++++++++++++++++------------------ 1 file changed, 1638 insertions(+), 1638 deletions(-) (limited to 'mp/src/game/shared/props_shared.cpp') diff --git a/mp/src/game/shared/props_shared.cpp b/mp/src/game/shared/props_shared.cpp index 86a8e865..7cf4489b 100644 --- a/mp/src/game/shared/props_shared.cpp +++ b/mp/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 -#include -#include - -#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 ¶ms, 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 ¶ms ); - -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(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(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 &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 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 ¶ms, 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(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 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(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 &list, int modelindex, float defBurstScale, int defCollisionGroup ) -{ - BreakModelList( list, modelindex, defBurstScale, defCollisionGroup ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &list - -// modelindex - -// *pPhysics - -// ¶ms - -// *pEntity - -// iPrecomputedBreakableCount - -// bIgnoreGibLImit - -// defaultLocation - -//----------------------------------------------------------------------------- -CBaseEntity *CreateGibsFromList( CUtlVector &list, int modelindex, IPhysicsObject *pPhysics, const breakablepropparams_t ¶ms, CBaseEntity *pEntity, int iPrecomputedBreakableCount, bool bIgnoreGibLimit, bool defaultLocation, CUtlVector *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(pPhysics->GetGameData()); - } - if ( pOwnerEntity ) - { - pOwnerAnim = dynamic_cast(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 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(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 +#include +#include + +#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 ¶ms, 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 ¶ms ); + +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(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(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 &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 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 ¶ms, 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(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 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(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 &list, int modelindex, float defBurstScale, int defCollisionGroup ) +{ + BreakModelList( list, modelindex, defBurstScale, defCollisionGroup ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &list - +// modelindex - +// *pPhysics - +// ¶ms - +// *pEntity - +// iPrecomputedBreakableCount - +// bIgnoreGibLImit - +// defaultLocation - +//----------------------------------------------------------------------------- +CBaseEntity *CreateGibsFromList( CUtlVector &list, int modelindex, IPhysicsObject *pPhysics, const breakablepropparams_t ¶ms, CBaseEntity *pEntity, int iPrecomputedBreakableCount, bool bIgnoreGibLimit, bool defaultLocation, CUtlVector *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(pPhysics->GetGameData()); + } + if ( pOwnerEntity ) + { + pOwnerAnim = dynamic_cast(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 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(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; +} + -- cgit v1.2.3