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/server/physics_impact_damage.cpp | 1484 +++++++++++++------------- 1 file changed, 742 insertions(+), 742 deletions(-) (limited to 'mp/src/game/server/physics_impact_damage.cpp') diff --git a/mp/src/game/server/physics_impact_damage.cpp b/mp/src/game/server/physics_impact_damage.cpp index 13c22d7d..164568e2 100644 --- a/mp/src/game/server/physics_impact_damage.cpp +++ b/mp/src/game/server/physics_impact_damage.cpp @@ -1,742 +1,742 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -#include "cbase.h" -#include "physics_impact_damage.h" -#include "shareddefs.h" -#include "vphysics/friction.h" -#include "vphysics/player_controller.h" -#include "world.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//============================================================================================== -// PLAYER PHYSICS DAMAGE TABLE -//============================================================================================== -static impactentry_t playerLinearTable[] = -{ - { 150*150, 5 }, - { 250*250, 10 }, - { 450*450, 20 }, - { 550*550, 50 }, - { 700*700, 100 }, - { 1000*1000, 500 }, -}; - -static impactentry_t playerAngularTable[] = -{ - { 100*100, 10 }, - { 150*150, 20 }, - { 200*200, 50 }, - { 300*300, 500 }, -}; - -impactdamagetable_t gDefaultPlayerImpactDamageTable = -{ - playerLinearTable, - playerAngularTable, - - ARRAYSIZE(playerLinearTable), - ARRAYSIZE(playerAngularTable), - - 24*24.0f, // minimum linear speed - 360*360.0f, // minimum angular speed - 2.0f, // can't take damage from anything under 2kg - - 5.0f, // anything less than 5kg is "small" - 5.0f, // never take more than 5 pts of damage from anything under 5kg - 36*36.0f, // <5kg objects must go faster than 36 in/s to do damage - - 0.0f, // large mass in kg (no large mass effects) - 1.0f, // large mass scale - 2.0f, // large mass falling scale - 320.0f, // min velocity for player speed to cause damage - -}; - -//============================================================================================== -// PLAYER-IN-VEHICLE PHYSICS DAMAGE TABLE -//============================================================================================== -static impactentry_t playerVehicleLinearTable[] = -{ - { 450*450, 5 }, - { 600*600, 10 }, - { 700*700, 25 }, - { 1000*1000, 50 }, - { 1500*1500, 100 }, - { 2000*2000, 500 }, -}; - -static impactentry_t playerVehicleAngularTable[] = -{ - { 100*100, 10 }, - { 150*150, 20 }, - { 200*200, 50 }, - { 300*300, 500 }, -}; - -impactdamagetable_t gDefaultPlayerVehicleImpactDamageTable = -{ - playerVehicleLinearTable, - playerVehicleAngularTable, - - ARRAYSIZE(playerVehicleLinearTable), - ARRAYSIZE(playerVehicleAngularTable), - - 24*24, // minimum linear speed - 360*360, // minimum angular speed - 80, // can't take damage from anything under 80 kg - - 150, // anything less than 150kg is "small" - 5, // never take more than 5 pts of damage from anything under 150kg - 36*36, // <150kg objects must go faster than 36 in/s to do damage - - 0, // large mass in kg (no large mass effects) - 1.0f, // large mass scale - 1.0f, // large mass falling scale - 0.0f, // min vel -}; - - -//============================================================================================== -// NPC PHYSICS DAMAGE TABLE -//============================================================================================== -static impactentry_t npcLinearTable[] = -{ - { 150*150, 5 }, - { 250*250, 10 }, - { 350*350, 50 }, - { 500*500, 100 }, - { 1000*1000, 500 }, -}; - -static impactentry_t npcAngularTable[] = -{ - { 100*100, 10 }, - { 150*150, 25 }, - { 200*200, 50 }, - { 250*250, 500 }, -}; - -impactdamagetable_t gDefaultNPCImpactDamageTable = -{ - npcLinearTable, - npcAngularTable, - - ARRAYSIZE(npcLinearTable), - ARRAYSIZE(npcAngularTable), - - 24*24, // minimum linear speed squared - 360*360, // minimum angular speed squared (360 deg/s to cause spin/slice damage) - 2, // can't take damage from anything under 2kg - - 5, // anything less than 5kg is "small" - 5, // never take more than 5 pts of damage from anything under 5kg - 36*36, // <5kg objects must go faster than 36 in/s to do damage - - VPHYSICS_LARGE_OBJECT_MASS, // large mass in kg - 4, // large mass scale (anything over 500kg does 4X as much energy to read from damage table) - 5, // large mass falling scale (emphasize falling/crushing damage over sideways impacts since the stress will kill you anyway) - 0.0f, // min vel -}; - -//============================================================================================== -// GLASS DAMAGE TABLE -//============================================================================================== -static impactentry_t glassLinearTable[] = -{ - { 25*25, 10 }, - { 50*50, 20 }, - { 100*100, 50 }, - { 200*200, 75 }, - { 500*500, 100 }, - { 250*250, 500 }, -}; - -static impactentry_t glassAngularTable[] = -{ - { 50*50, 25 }, - { 100*100, 50 }, - { 200*200, 100 }, - { 250*250, 500 }, -}; - -impactdamagetable_t gGlassImpactDamageTable = -{ - glassLinearTable, - glassAngularTable, - - ARRAYSIZE(glassLinearTable), - ARRAYSIZE(glassAngularTable), - - 8*8, // minimum linear speed squared - 360*360, // minimum angular speed squared (360 deg/s to cause spin/slice damage) - 2, // can't take damage from anything under 2kg - - 1, // anything less than 1kg is "small" - 10, // never take more than 10 pts of damage from anything under 1kg - 8*8, // <1kg objects must go faster than 8 in/s to do damage - - 50, // large mass in kg - 4, // large mass scale (anything over 50kg does 4X as much energy to read from damage table) - 0.0f, // min vel -}; - -//============================================================================================== -// PHYSICS TABLE NAMES -//============================================================================================== -struct damagetable_t -{ - const char *pszTableName; - impactdamagetable_t *pTable; -}; - -static damagetable_t gDamageTableRegistry[] = -{ - { - "player", - &gDefaultPlayerImpactDamageTable, - }, - { - "player_vehicle", - &gDefaultPlayerVehicleImpactDamageTable, - }, - { - "npc", - &gDefaultNPCImpactDamageTable, - }, - { - "glass", - &gGlassImpactDamageTable, - }, -}; - -//============================================================================================== -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -float ReadDamageTable( impactentry_t *pTable, int tableCount, float impulse, bool bDebug ) -{ - if ( pTable ) - { - int i; - for ( i = 0; i < tableCount; i++ ) - { - if ( impulse < pTable[i].impulse ) - break; - } - if ( i > 0 ) - { - i--; - if ( bDebug ) - { - Msg("Damage %.0f, energy %.0f\n", pTable[i].damage, FastSqrt(impulse) ); - } - return pTable[i].damage; - } - } - return 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -float CalculatePhysicsImpactDamage( int index, gamevcollisionevent_t *pEvent, const impactdamagetable_t &table, float energyScale, bool allowStaticDamage, int &damageType, bool bDamageFromHeldObjects ) -{ - damageType = DMG_CRUSH; - int otherIndex = !index; - - // UNDONE: Expose a flag for self-inflicted damage? Can't think of a valid case so far. - if ( pEvent->pEntities[0] == pEvent->pEntities[1] ) - return 0; - - if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_NO_NPC_IMPACT_DMG ) - { - if( pEvent->pEntities[index]->IsNPC() || pEvent->pEntities[index]->IsPlayer() ) - { - return 0; - } - } - - // use implicit velocities on ragdolls since they may have high constraint velocities that aren't actually executed, just pushed through contacts - if (( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_PART_OF_RAGDOLL) && pEvent->pEntities[index]->IsPlayer() ) - { - pEvent->pObjects[otherIndex]->GetImplicitVelocity( &pEvent->preVelocity[otherIndex], &pEvent->preAngularVelocity[otherIndex] ); - } - - // Dissolving impact damage results in death always. - if ( ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_DMG_DISSOLVE ) && - !pEvent->pEntities[index]->IsEFlagSet(EFL_NO_DISSOLVE) ) - { - damageType |= DMG_DISSOLVE; - return 1000; - } - - if ( energyScale <= 0.0f ) - return 0; - - const int gameFlagsNoDamage = FVPHYSICS_CONSTRAINT_STATIC | FVPHYSICS_NO_IMPACT_DMG; - - // NOTE: Crushing damage is handled by stress calcs in vphysics update functions, this is ONLY impact damage - // this is a non-moving object due to a constraint - no damage - if ( pEvent->pObjects[otherIndex]->GetGameFlags() & gameFlagsNoDamage ) - return 0; - - // If it doesn't take damage from held objects and the object is being held - no damage - if ( !bDamageFromHeldObjects && ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) ) - { - // If it doesn't take damage from held objects - no damage - if ( !bDamageFromHeldObjects ) - return 0; - } - - if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_MULTIOBJECT_ENTITY ) - { - // UNDONE: Add up mass here for car wheels and prop_ragdoll pieces? - IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; - int count = pEvent->pEntities[otherIndex]->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); - for ( int i = 0; i < count; i++ ) - { - if ( pList[i]->GetGameFlags() & gameFlagsNoDamage ) - return 0; - } - } - - if ( pEvent->pObjects[index]->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) - { - // players can't damage held objects - if ( pEvent->pEntities[otherIndex]->IsPlayer() ) - return 0; - - allowStaticDamage = false; - } - -#if 0 - { - PhysGetDamageInflictorVelocityStartOfFrame( pEvent->pObjects[otherIndex], pEvent->preVelocity[otherIndex], pEvent->preAngularVelocity[otherIndex] ); - } -#endif - - float otherSpeedSqr = pEvent->preVelocity[otherIndex].LengthSqr(); - float otherAngSqr = 0; - - // factor in angular for sharp objects - if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_DMG_SLICE ) - { - otherAngSqr = pEvent->preAngularVelocity[otherIndex].LengthSqr(); - } - - float otherMass = pEvent->pObjects[otherIndex]->GetMass(); - - if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) - { - if ( gpGlobals->maxClients == 1 ) - { - // if the player is holding the object, use it's real mass (player holding reduced the mass) - CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); - if ( pPlayer ) - { - otherMass = pPlayer->GetHeldObjectMass( pEvent->pObjects[otherIndex] ); - } - } - } - - // NOTE: sum the mass of each object in this system for the purpose of damage - if ( pEvent->pEntities[otherIndex] && (pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_MULTIOBJECT_ENTITY) ) - { - otherMass = PhysGetEntityMass( pEvent->pEntities[otherIndex] ); - } - - if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_HEAVY_OBJECT ) - { - otherMass = table.largeMassMin; - if ( energyScale < 2.0f ) - { - energyScale = 2.0f; - } - } - - // UNDONE: allowStaticDamage is a hack - work out some method for - // breakable props to impact the world and break!! - if ( !allowStaticDamage ) - { - if ( otherMass < table.minMass ) - return 0; - // check to see if the object is small - if ( otherMass < table.smallMassMax && otherSpeedSqr < table.smallMassMinSpeedSqr ) - return 0; - - if ( otherSpeedSqr < table.minSpeedSqr && otherAngSqr < table.minRotSpeedSqr ) - return 0; - } - - // Add extra oomph for floating objects - if ( pEvent->pEntities[index]->IsFloating() && !pEvent->pEntities[otherIndex]->IsWorld() ) - { - if ( energyScale < 3.0f ) - { - energyScale = 3.0f; - } - } - - float damage = 0; - bool bDebug = false;//(&table == &gDefaultPlayerImpactDamageTable); - - // don't ever take spin damage from slowly spinning objects - if ( otherAngSqr > table.minRotSpeedSqr ) - { - Vector otherInertia = pEvent->pObjects[otherIndex]->GetInertia(); - float angularMom = DotProductAbs( otherInertia, pEvent->preAngularVelocity[otherIndex] ); - damage = ReadDamageTable( table.angularTable, table.angularCount, angularMom * energyScale, bDebug ); - if ( damage > 0 ) - { -// Msg("Spin : %.1f, Damage %.0f\n", FastSqrt(angularMom), damage ); - damageType |= DMG_SLASH; - } - } - - float deltaV = pEvent->preVelocity[index].Length() - pEvent->postVelocity[index].Length(); - float mass = pEvent->pObjects[index]->GetMass(); - - // If I lost speed, and I lost less than min velocity, then filter out this energy - if ( deltaV > 0 && deltaV < table.myMinVelocity ) - { - deltaV = 0; - } - float eliminatedEnergy = deltaV * deltaV * mass; - - deltaV = pEvent->preVelocity[otherIndex].Length() - pEvent->postVelocity[otherIndex].Length(); - float otherEliminatedEnergy = deltaV * deltaV * otherMass; - - // exaggerate the effects of really large objects - if ( otherMass >= table.largeMassMin ) - { - otherEliminatedEnergy *= table.largeMassScale; - float dz = pEvent->preVelocity[otherIndex].z - pEvent->postVelocity[otherIndex].z; - - if ( deltaV > 0 && dz < 0 && pEvent->preVelocity[otherIndex].z < 0 ) - { - float factor = fabs(dz / deltaV); - otherEliminatedEnergy *= (1 + factor * (table.largeMassFallingScale - 1.0f)); - } - } - - eliminatedEnergy += otherEliminatedEnergy; - - // now in units of this character's speed squared - float invMass = pEvent->pObjects[index]->GetInvMass(); - if ( !pEvent->pObjects[index]->IsMoveable() ) - { - // inv mass is zero, but impact damage is enabled on this - // prop, so recompute: - invMass = 1.0f / pEvent->pObjects[index]->GetMass(); - } - else if ( pEvent->pObjects[index]->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) - { - if ( gpGlobals->maxClients == 1 ) - { - // if the player is holding the object, use it's real mass (player holding reduced the mass) - CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); - if ( pPlayer ) - { - float mass = pPlayer->GetHeldObjectMass( pEvent->pObjects[index] ); - if ( mass > 0 ) - { - invMass = 1.0f / mass; - } - } - } - } - - eliminatedEnergy *= invMass * energyScale; - - damage += ReadDamageTable( table.linearTable, table.linearCount, eliminatedEnergy, bDebug ); - - if ( !pEvent->pObjects[otherIndex]->IsStatic() && otherMass < table.smallMassMax && table.smallMassCap > 0 ) - { - damage = clamp( damage, 0.f, table.smallMassCap ); - } - - return damage; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -float CalculateDefaultPhysicsDamage( int index, gamevcollisionevent_t *pEvent, float energyScale, bool allowStaticDamage, int &damageType, string_t iszDamageTableName, bool bDamageFromHeldObjects ) -{ - // If we have a specified damage table, find it and use it instead - if ( iszDamageTableName != NULL_STRING ) - { - for ( int i = 0; i < ARRAYSIZE(gDamageTableRegistry); i++ ) - { - if ( !Q_strcmp( gDamageTableRegistry[i].pszTableName, STRING(iszDamageTableName) ) ) - return CalculatePhysicsImpactDamage( index, pEvent, *(gDamageTableRegistry[i].pTable), energyScale, allowStaticDamage, damageType, bDamageFromHeldObjects ); - } - - Warning("Failed to find custom physics damage table name: %s\n", STRING(iszDamageTableName) ); - } - - return CalculatePhysicsImpactDamage( index, pEvent, gDefaultNPCImpactDamageTable, energyScale, allowStaticDamage, damageType, bDamageFromHeldObjects ); -} - -static bool IsPhysicallyControlled( CBaseEntity *pEntity, IPhysicsObject *pPhysics ) -{ - bool isPhysical = false; - if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS ) - { - isPhysical = true; - } - else - { - if ( pPhysics->GetShadowController() ) - { - isPhysical = pPhysics->GetShadowController()->IsPhysicallyControlled(); - } - } - return isPhysical; -} -float CalculateObjectStress( IPhysicsObject *pObject, CBaseEntity *pInputOwnerEntity, vphysics_objectstress_t *pOutput ) -{ - CUtlVector< CBaseEntity * > pObjectList; - CUtlVector< Vector > objectForce; - bool hasLargeObject = false; - - // add a slot for static objects - pObjectList.AddToTail( NULL ); - objectForce.AddToTail( vec3_origin ); - // add a slot for friendly objects - pObjectList.AddToTail( NULL ); - objectForce.AddToTail( vec3_origin ); - - CBaseCombatCharacter *pBCC = pInputOwnerEntity->MyCombatCharacterPointer(); - - IPhysicsFrictionSnapshot *pSnapshot = pObject->CreateFrictionSnapshot(); - float objMass = pObject->GetMass(); - while ( pSnapshot->IsValid() ) - { - float force = pSnapshot->GetNormalForce(); - if ( force > 0.0f ) - { - IPhysicsObject *pOther = pSnapshot->GetObject(1); - CBaseEntity *pOtherEntity = static_cast(pOther->GetGameData()); - if ( !pOtherEntity ) - { - // object was just deleted, but we still have a contact point this frame... - // just assume it came from the world. - pOtherEntity = GetWorldEntity(); - } - CBaseEntity *pOtherOwner = pOtherEntity; - if ( pOtherEntity->GetOwnerEntity() ) - { - pOtherOwner = pOtherEntity->GetOwnerEntity(); - } - - int outIndex = 0; - if ( !pOther->IsMoveable() ) - { - outIndex = 0; - } - // NavIgnored objects are often being pushed by a friendly - else if ( pBCC && (pBCC->IRelationType( pOtherOwner ) == D_LI || pOtherEntity->IsNavIgnored()) ) - { - outIndex = 1; - } - // player held objects do no stress - else if ( pOther->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) - { - outIndex = 1; - } - else - { - if ( pOther->GetMass() >= VPHYSICS_LARGE_OBJECT_MASS ) - { - if ( pInputOwnerEntity->GetGroundEntity() != pOtherEntity) - { - hasLargeObject = true; - } - } - // moveable, non-friendly - - // aggregate contacts over each object to avoid greater stress in multiple contact cases - // NOTE: Contacts should be in order, so this shouldn't ever search, but just in case - outIndex = pObjectList.Count(); - for ( int i = pObjectList.Count()-1; i >= 2; --i ) - { - if ( pObjectList[i] == pOtherOwner ) - { - outIndex = i; - break; - } - } - if ( outIndex == pObjectList.Count() ) - { - pObjectList.AddToTail( pOtherOwner ); - objectForce.AddToTail( vec3_origin ); - } - } - - if ( outIndex != 0 && pInputOwnerEntity->GetMoveType() != MOVETYPE_VPHYSICS && !IsPhysicallyControlled(pOtherEntity, pOther) ) - { - // UNDONE: Test this! This is to remove any shadow/shadow stress. The game should handle this with blocked/damage - force = 0.0f; - } - - Vector normal; - pSnapshot->GetSurfaceNormal( normal ); - objectForce[outIndex] += normal * force; - } - pSnapshot->NextFrictionData(); - } - pObject->DestroyFrictionSnapshot( pSnapshot ); - pSnapshot = NULL; - - // clear out all friendly force - objectForce[1].Init(); - - float sum = 0; - Vector negativeForce = vec3_origin; - Vector positiveForce = vec3_origin; - - Assert( pObjectList.Count() == objectForce.Count() ); - for ( int objectIndex = pObjectList.Count()-1; objectIndex >= 0; --objectIndex ) - { - sum += objectForce[objectIndex].Length(); - for ( int i = 0; i < 3; i++ ) - { - if ( objectForce[objectIndex][i] < 0 ) - { - negativeForce[i] -= objectForce[objectIndex][i]; - } - else - { - positiveForce[i] += objectForce[objectIndex][i]; - } - } - } - - // "external" stress is two way (something pushes on the object and something else pushes back) - // so the set of minimum values per component are the projections of the two-way force - // "internal" stress is one way (the object is pushing against something OR something pushing back) - // the momentum must have come from inside the object (gravity, controller, etc) - Vector internalForce = vec3_origin; - Vector externalForce = vec3_origin; - - for ( int i = 0; i < 3; i++ ) - { - if ( negativeForce[i] < positiveForce[i] ) - { - internalForce[i] = positiveForce[i] - negativeForce[i]; - externalForce[i] = negativeForce[i]; - } - else - { - internalForce[i] = negativeForce[i] - positiveForce[i]; - externalForce[i] = positiveForce[i]; - } - } - - // sum is kg in / s - Vector gravVector; - physenv->GetGravity( &gravVector ); - float gravity = gravVector.Length(); - if ( pInputOwnerEntity->GetMoveType() != MOVETYPE_VPHYSICS && pObject->IsMoveable() ) - { - Vector lastVel; - lastVel.Init(); - if ( pObject->GetShadowController() ) - { - pObject->GetShadowController()->GetLastImpulse( &lastVel ); - } - else - { - if ( ( pObject->GetCallbackFlags() & CALLBACK_IS_PLAYER_CONTROLLER ) ) - { - CBasePlayer *pPlayer = ToBasePlayer( pInputOwnerEntity ); - IPhysicsPlayerController *pController = pPlayer ? pPlayer->GetPhysicsController() : NULL; - if ( pController ) - { - pController->GetLastImpulse( &lastVel ); - } - } - } - - // Work in progress... - - // Peek into the controller for this object. Look at the input velocity and make sure it's all - // accounted for in the computed stress. If not, redistribute external to internal as it's - // probably being reflected in a way we can't measure here. - float inputLen = lastVel.Length() * (1.0f / physenv->GetSimulationTimestep()) * objMass; - if ( inputLen > 0.0f ) - { - float internalLen = internalForce.Length(); - if ( internalLen < inputLen ) - { - float ratio = internalLen / inputLen; - Vector delta = internalForce * (1.0f - ratio); - internalForce += delta; - float deltaLen = delta.Length(); - sum -= deltaLen; - float extLen = VectorNormalize(externalForce) - deltaLen; - if ( extLen < 0 ) - { - extLen = 0; - } - externalForce *= extLen; - } - } - } - - float invGravity = gravity; - if ( invGravity <= 0 ) - { - invGravity = 1.0f; - } - else - { - invGravity = 1.0f / invGravity; - } - sum *= invGravity; - internalForce *= invGravity; - externalForce *= invGravity; - if ( !pObject->IsMoveable() ) - { - // the above algorithm will see almost all force as internal if the object is not moveable - // (it doesn't push on anything else, so nothing is reciprocated) - // exceptions for friction of a single other object with multiple contact points on this object - - // But the game wants to see it all as external because obviously the object can't move, so it can't have - // internal stress - externalForce = internalForce; - internalForce.Init(); - - if ( !pObject->IsStatic() ) - { - sum += objMass; - } - } - else - { - // assume object is at rest - if ( sum > objMass ) - { - sum = objMass + (sum-objMass) * 0.5; - } - } - - if ( pOutput ) - { - pOutput->exertedStress = internalForce.Length(); - pOutput->receivedStress = externalForce.Length(); - pOutput->hasNonStaticStress = pObjectList.Count() > 2 ? true : false; - pOutput->hasLargeObjectContact = hasLargeObject; - } - - // sum is now kg - return sum; -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "physics_impact_damage.h" +#include "shareddefs.h" +#include "vphysics/friction.h" +#include "vphysics/player_controller.h" +#include "world.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//============================================================================================== +// PLAYER PHYSICS DAMAGE TABLE +//============================================================================================== +static impactentry_t playerLinearTable[] = +{ + { 150*150, 5 }, + { 250*250, 10 }, + { 450*450, 20 }, + { 550*550, 50 }, + { 700*700, 100 }, + { 1000*1000, 500 }, +}; + +static impactentry_t playerAngularTable[] = +{ + { 100*100, 10 }, + { 150*150, 20 }, + { 200*200, 50 }, + { 300*300, 500 }, +}; + +impactdamagetable_t gDefaultPlayerImpactDamageTable = +{ + playerLinearTable, + playerAngularTable, + + ARRAYSIZE(playerLinearTable), + ARRAYSIZE(playerAngularTable), + + 24*24.0f, // minimum linear speed + 360*360.0f, // minimum angular speed + 2.0f, // can't take damage from anything under 2kg + + 5.0f, // anything less than 5kg is "small" + 5.0f, // never take more than 5 pts of damage from anything under 5kg + 36*36.0f, // <5kg objects must go faster than 36 in/s to do damage + + 0.0f, // large mass in kg (no large mass effects) + 1.0f, // large mass scale + 2.0f, // large mass falling scale + 320.0f, // min velocity for player speed to cause damage + +}; + +//============================================================================================== +// PLAYER-IN-VEHICLE PHYSICS DAMAGE TABLE +//============================================================================================== +static impactentry_t playerVehicleLinearTable[] = +{ + { 450*450, 5 }, + { 600*600, 10 }, + { 700*700, 25 }, + { 1000*1000, 50 }, + { 1500*1500, 100 }, + { 2000*2000, 500 }, +}; + +static impactentry_t playerVehicleAngularTable[] = +{ + { 100*100, 10 }, + { 150*150, 20 }, + { 200*200, 50 }, + { 300*300, 500 }, +}; + +impactdamagetable_t gDefaultPlayerVehicleImpactDamageTable = +{ + playerVehicleLinearTable, + playerVehicleAngularTable, + + ARRAYSIZE(playerVehicleLinearTable), + ARRAYSIZE(playerVehicleAngularTable), + + 24*24, // minimum linear speed + 360*360, // minimum angular speed + 80, // can't take damage from anything under 80 kg + + 150, // anything less than 150kg is "small" + 5, // never take more than 5 pts of damage from anything under 150kg + 36*36, // <150kg objects must go faster than 36 in/s to do damage + + 0, // large mass in kg (no large mass effects) + 1.0f, // large mass scale + 1.0f, // large mass falling scale + 0.0f, // min vel +}; + + +//============================================================================================== +// NPC PHYSICS DAMAGE TABLE +//============================================================================================== +static impactentry_t npcLinearTable[] = +{ + { 150*150, 5 }, + { 250*250, 10 }, + { 350*350, 50 }, + { 500*500, 100 }, + { 1000*1000, 500 }, +}; + +static impactentry_t npcAngularTable[] = +{ + { 100*100, 10 }, + { 150*150, 25 }, + { 200*200, 50 }, + { 250*250, 500 }, +}; + +impactdamagetable_t gDefaultNPCImpactDamageTable = +{ + npcLinearTable, + npcAngularTable, + + ARRAYSIZE(npcLinearTable), + ARRAYSIZE(npcAngularTable), + + 24*24, // minimum linear speed squared + 360*360, // minimum angular speed squared (360 deg/s to cause spin/slice damage) + 2, // can't take damage from anything under 2kg + + 5, // anything less than 5kg is "small" + 5, // never take more than 5 pts of damage from anything under 5kg + 36*36, // <5kg objects must go faster than 36 in/s to do damage + + VPHYSICS_LARGE_OBJECT_MASS, // large mass in kg + 4, // large mass scale (anything over 500kg does 4X as much energy to read from damage table) + 5, // large mass falling scale (emphasize falling/crushing damage over sideways impacts since the stress will kill you anyway) + 0.0f, // min vel +}; + +//============================================================================================== +// GLASS DAMAGE TABLE +//============================================================================================== +static impactentry_t glassLinearTable[] = +{ + { 25*25, 10 }, + { 50*50, 20 }, + { 100*100, 50 }, + { 200*200, 75 }, + { 500*500, 100 }, + { 250*250, 500 }, +}; + +static impactentry_t glassAngularTable[] = +{ + { 50*50, 25 }, + { 100*100, 50 }, + { 200*200, 100 }, + { 250*250, 500 }, +}; + +impactdamagetable_t gGlassImpactDamageTable = +{ + glassLinearTable, + glassAngularTable, + + ARRAYSIZE(glassLinearTable), + ARRAYSIZE(glassAngularTable), + + 8*8, // minimum linear speed squared + 360*360, // minimum angular speed squared (360 deg/s to cause spin/slice damage) + 2, // can't take damage from anything under 2kg + + 1, // anything less than 1kg is "small" + 10, // never take more than 10 pts of damage from anything under 1kg + 8*8, // <1kg objects must go faster than 8 in/s to do damage + + 50, // large mass in kg + 4, // large mass scale (anything over 50kg does 4X as much energy to read from damage table) + 0.0f, // min vel +}; + +//============================================================================================== +// PHYSICS TABLE NAMES +//============================================================================================== +struct damagetable_t +{ + const char *pszTableName; + impactdamagetable_t *pTable; +}; + +static damagetable_t gDamageTableRegistry[] = +{ + { + "player", + &gDefaultPlayerImpactDamageTable, + }, + { + "player_vehicle", + &gDefaultPlayerVehicleImpactDamageTable, + }, + { + "npc", + &gDefaultNPCImpactDamageTable, + }, + { + "glass", + &gGlassImpactDamageTable, + }, +}; + +//============================================================================================== +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float ReadDamageTable( impactentry_t *pTable, int tableCount, float impulse, bool bDebug ) +{ + if ( pTable ) + { + int i; + for ( i = 0; i < tableCount; i++ ) + { + if ( impulse < pTable[i].impulse ) + break; + } + if ( i > 0 ) + { + i--; + if ( bDebug ) + { + Msg("Damage %.0f, energy %.0f\n", pTable[i].damage, FastSqrt(impulse) ); + } + return pTable[i].damage; + } + } + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CalculatePhysicsImpactDamage( int index, gamevcollisionevent_t *pEvent, const impactdamagetable_t &table, float energyScale, bool allowStaticDamage, int &damageType, bool bDamageFromHeldObjects ) +{ + damageType = DMG_CRUSH; + int otherIndex = !index; + + // UNDONE: Expose a flag for self-inflicted damage? Can't think of a valid case so far. + if ( pEvent->pEntities[0] == pEvent->pEntities[1] ) + return 0; + + if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_NO_NPC_IMPACT_DMG ) + { + if( pEvent->pEntities[index]->IsNPC() || pEvent->pEntities[index]->IsPlayer() ) + { + return 0; + } + } + + // use implicit velocities on ragdolls since they may have high constraint velocities that aren't actually executed, just pushed through contacts + if (( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_PART_OF_RAGDOLL) && pEvent->pEntities[index]->IsPlayer() ) + { + pEvent->pObjects[otherIndex]->GetImplicitVelocity( &pEvent->preVelocity[otherIndex], &pEvent->preAngularVelocity[otherIndex] ); + } + + // Dissolving impact damage results in death always. + if ( ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_DMG_DISSOLVE ) && + !pEvent->pEntities[index]->IsEFlagSet(EFL_NO_DISSOLVE) ) + { + damageType |= DMG_DISSOLVE; + return 1000; + } + + if ( energyScale <= 0.0f ) + return 0; + + const int gameFlagsNoDamage = FVPHYSICS_CONSTRAINT_STATIC | FVPHYSICS_NO_IMPACT_DMG; + + // NOTE: Crushing damage is handled by stress calcs in vphysics update functions, this is ONLY impact damage + // this is a non-moving object due to a constraint - no damage + if ( pEvent->pObjects[otherIndex]->GetGameFlags() & gameFlagsNoDamage ) + return 0; + + // If it doesn't take damage from held objects and the object is being held - no damage + if ( !bDamageFromHeldObjects && ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) ) + { + // If it doesn't take damage from held objects - no damage + if ( !bDamageFromHeldObjects ) + return 0; + } + + if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_MULTIOBJECT_ENTITY ) + { + // UNDONE: Add up mass here for car wheels and prop_ragdoll pieces? + IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + int count = pEvent->pEntities[otherIndex]->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); + for ( int i = 0; i < count; i++ ) + { + if ( pList[i]->GetGameFlags() & gameFlagsNoDamage ) + return 0; + } + } + + if ( pEvent->pObjects[index]->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) + { + // players can't damage held objects + if ( pEvent->pEntities[otherIndex]->IsPlayer() ) + return 0; + + allowStaticDamage = false; + } + +#if 0 + { + PhysGetDamageInflictorVelocityStartOfFrame( pEvent->pObjects[otherIndex], pEvent->preVelocity[otherIndex], pEvent->preAngularVelocity[otherIndex] ); + } +#endif + + float otherSpeedSqr = pEvent->preVelocity[otherIndex].LengthSqr(); + float otherAngSqr = 0; + + // factor in angular for sharp objects + if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_DMG_SLICE ) + { + otherAngSqr = pEvent->preAngularVelocity[otherIndex].LengthSqr(); + } + + float otherMass = pEvent->pObjects[otherIndex]->GetMass(); + + if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) + { + if ( gpGlobals->maxClients == 1 ) + { + // if the player is holding the object, use it's real mass (player holding reduced the mass) + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if ( pPlayer ) + { + otherMass = pPlayer->GetHeldObjectMass( pEvent->pObjects[otherIndex] ); + } + } + } + + // NOTE: sum the mass of each object in this system for the purpose of damage + if ( pEvent->pEntities[otherIndex] && (pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_MULTIOBJECT_ENTITY) ) + { + otherMass = PhysGetEntityMass( pEvent->pEntities[otherIndex] ); + } + + if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_HEAVY_OBJECT ) + { + otherMass = table.largeMassMin; + if ( energyScale < 2.0f ) + { + energyScale = 2.0f; + } + } + + // UNDONE: allowStaticDamage is a hack - work out some method for + // breakable props to impact the world and break!! + if ( !allowStaticDamage ) + { + if ( otherMass < table.minMass ) + return 0; + // check to see if the object is small + if ( otherMass < table.smallMassMax && otherSpeedSqr < table.smallMassMinSpeedSqr ) + return 0; + + if ( otherSpeedSqr < table.minSpeedSqr && otherAngSqr < table.minRotSpeedSqr ) + return 0; + } + + // Add extra oomph for floating objects + if ( pEvent->pEntities[index]->IsFloating() && !pEvent->pEntities[otherIndex]->IsWorld() ) + { + if ( energyScale < 3.0f ) + { + energyScale = 3.0f; + } + } + + float damage = 0; + bool bDebug = false;//(&table == &gDefaultPlayerImpactDamageTable); + + // don't ever take spin damage from slowly spinning objects + if ( otherAngSqr > table.minRotSpeedSqr ) + { + Vector otherInertia = pEvent->pObjects[otherIndex]->GetInertia(); + float angularMom = DotProductAbs( otherInertia, pEvent->preAngularVelocity[otherIndex] ); + damage = ReadDamageTable( table.angularTable, table.angularCount, angularMom * energyScale, bDebug ); + if ( damage > 0 ) + { +// Msg("Spin : %.1f, Damage %.0f\n", FastSqrt(angularMom), damage ); + damageType |= DMG_SLASH; + } + } + + float deltaV = pEvent->preVelocity[index].Length() - pEvent->postVelocity[index].Length(); + float mass = pEvent->pObjects[index]->GetMass(); + + // If I lost speed, and I lost less than min velocity, then filter out this energy + if ( deltaV > 0 && deltaV < table.myMinVelocity ) + { + deltaV = 0; + } + float eliminatedEnergy = deltaV * deltaV * mass; + + deltaV = pEvent->preVelocity[otherIndex].Length() - pEvent->postVelocity[otherIndex].Length(); + float otherEliminatedEnergy = deltaV * deltaV * otherMass; + + // exaggerate the effects of really large objects + if ( otherMass >= table.largeMassMin ) + { + otherEliminatedEnergy *= table.largeMassScale; + float dz = pEvent->preVelocity[otherIndex].z - pEvent->postVelocity[otherIndex].z; + + if ( deltaV > 0 && dz < 0 && pEvent->preVelocity[otherIndex].z < 0 ) + { + float factor = fabs(dz / deltaV); + otherEliminatedEnergy *= (1 + factor * (table.largeMassFallingScale - 1.0f)); + } + } + + eliminatedEnergy += otherEliminatedEnergy; + + // now in units of this character's speed squared + float invMass = pEvent->pObjects[index]->GetInvMass(); + if ( !pEvent->pObjects[index]->IsMoveable() ) + { + // inv mass is zero, but impact damage is enabled on this + // prop, so recompute: + invMass = 1.0f / pEvent->pObjects[index]->GetMass(); + } + else if ( pEvent->pObjects[index]->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) + { + if ( gpGlobals->maxClients == 1 ) + { + // if the player is holding the object, use it's real mass (player holding reduced the mass) + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if ( pPlayer ) + { + float mass = pPlayer->GetHeldObjectMass( pEvent->pObjects[index] ); + if ( mass > 0 ) + { + invMass = 1.0f / mass; + } + } + } + } + + eliminatedEnergy *= invMass * energyScale; + + damage += ReadDamageTable( table.linearTable, table.linearCount, eliminatedEnergy, bDebug ); + + if ( !pEvent->pObjects[otherIndex]->IsStatic() && otherMass < table.smallMassMax && table.smallMassCap > 0 ) + { + damage = clamp( damage, 0.f, table.smallMassCap ); + } + + return damage; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CalculateDefaultPhysicsDamage( int index, gamevcollisionevent_t *pEvent, float energyScale, bool allowStaticDamage, int &damageType, string_t iszDamageTableName, bool bDamageFromHeldObjects ) +{ + // If we have a specified damage table, find it and use it instead + if ( iszDamageTableName != NULL_STRING ) + { + for ( int i = 0; i < ARRAYSIZE(gDamageTableRegistry); i++ ) + { + if ( !Q_strcmp( gDamageTableRegistry[i].pszTableName, STRING(iszDamageTableName) ) ) + return CalculatePhysicsImpactDamage( index, pEvent, *(gDamageTableRegistry[i].pTable), energyScale, allowStaticDamage, damageType, bDamageFromHeldObjects ); + } + + Warning("Failed to find custom physics damage table name: %s\n", STRING(iszDamageTableName) ); + } + + return CalculatePhysicsImpactDamage( index, pEvent, gDefaultNPCImpactDamageTable, energyScale, allowStaticDamage, damageType, bDamageFromHeldObjects ); +} + +static bool IsPhysicallyControlled( CBaseEntity *pEntity, IPhysicsObject *pPhysics ) +{ + bool isPhysical = false; + if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS ) + { + isPhysical = true; + } + else + { + if ( pPhysics->GetShadowController() ) + { + isPhysical = pPhysics->GetShadowController()->IsPhysicallyControlled(); + } + } + return isPhysical; +} +float CalculateObjectStress( IPhysicsObject *pObject, CBaseEntity *pInputOwnerEntity, vphysics_objectstress_t *pOutput ) +{ + CUtlVector< CBaseEntity * > pObjectList; + CUtlVector< Vector > objectForce; + bool hasLargeObject = false; + + // add a slot for static objects + pObjectList.AddToTail( NULL ); + objectForce.AddToTail( vec3_origin ); + // add a slot for friendly objects + pObjectList.AddToTail( NULL ); + objectForce.AddToTail( vec3_origin ); + + CBaseCombatCharacter *pBCC = pInputOwnerEntity->MyCombatCharacterPointer(); + + IPhysicsFrictionSnapshot *pSnapshot = pObject->CreateFrictionSnapshot(); + float objMass = pObject->GetMass(); + while ( pSnapshot->IsValid() ) + { + float force = pSnapshot->GetNormalForce(); + if ( force > 0.0f ) + { + IPhysicsObject *pOther = pSnapshot->GetObject(1); + CBaseEntity *pOtherEntity = static_cast(pOther->GetGameData()); + if ( !pOtherEntity ) + { + // object was just deleted, but we still have a contact point this frame... + // just assume it came from the world. + pOtherEntity = GetWorldEntity(); + } + CBaseEntity *pOtherOwner = pOtherEntity; + if ( pOtherEntity->GetOwnerEntity() ) + { + pOtherOwner = pOtherEntity->GetOwnerEntity(); + } + + int outIndex = 0; + if ( !pOther->IsMoveable() ) + { + outIndex = 0; + } + // NavIgnored objects are often being pushed by a friendly + else if ( pBCC && (pBCC->IRelationType( pOtherOwner ) == D_LI || pOtherEntity->IsNavIgnored()) ) + { + outIndex = 1; + } + // player held objects do no stress + else if ( pOther->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) + { + outIndex = 1; + } + else + { + if ( pOther->GetMass() >= VPHYSICS_LARGE_OBJECT_MASS ) + { + if ( pInputOwnerEntity->GetGroundEntity() != pOtherEntity) + { + hasLargeObject = true; + } + } + // moveable, non-friendly + + // aggregate contacts over each object to avoid greater stress in multiple contact cases + // NOTE: Contacts should be in order, so this shouldn't ever search, but just in case + outIndex = pObjectList.Count(); + for ( int i = pObjectList.Count()-1; i >= 2; --i ) + { + if ( pObjectList[i] == pOtherOwner ) + { + outIndex = i; + break; + } + } + if ( outIndex == pObjectList.Count() ) + { + pObjectList.AddToTail( pOtherOwner ); + objectForce.AddToTail( vec3_origin ); + } + } + + if ( outIndex != 0 && pInputOwnerEntity->GetMoveType() != MOVETYPE_VPHYSICS && !IsPhysicallyControlled(pOtherEntity, pOther) ) + { + // UNDONE: Test this! This is to remove any shadow/shadow stress. The game should handle this with blocked/damage + force = 0.0f; + } + + Vector normal; + pSnapshot->GetSurfaceNormal( normal ); + objectForce[outIndex] += normal * force; + } + pSnapshot->NextFrictionData(); + } + pObject->DestroyFrictionSnapshot( pSnapshot ); + pSnapshot = NULL; + + // clear out all friendly force + objectForce[1].Init(); + + float sum = 0; + Vector negativeForce = vec3_origin; + Vector positiveForce = vec3_origin; + + Assert( pObjectList.Count() == objectForce.Count() ); + for ( int objectIndex = pObjectList.Count()-1; objectIndex >= 0; --objectIndex ) + { + sum += objectForce[objectIndex].Length(); + for ( int i = 0; i < 3; i++ ) + { + if ( objectForce[objectIndex][i] < 0 ) + { + negativeForce[i] -= objectForce[objectIndex][i]; + } + else + { + positiveForce[i] += objectForce[objectIndex][i]; + } + } + } + + // "external" stress is two way (something pushes on the object and something else pushes back) + // so the set of minimum values per component are the projections of the two-way force + // "internal" stress is one way (the object is pushing against something OR something pushing back) + // the momentum must have come from inside the object (gravity, controller, etc) + Vector internalForce = vec3_origin; + Vector externalForce = vec3_origin; + + for ( int i = 0; i < 3; i++ ) + { + if ( negativeForce[i] < positiveForce[i] ) + { + internalForce[i] = positiveForce[i] - negativeForce[i]; + externalForce[i] = negativeForce[i]; + } + else + { + internalForce[i] = negativeForce[i] - positiveForce[i]; + externalForce[i] = positiveForce[i]; + } + } + + // sum is kg in / s + Vector gravVector; + physenv->GetGravity( &gravVector ); + float gravity = gravVector.Length(); + if ( pInputOwnerEntity->GetMoveType() != MOVETYPE_VPHYSICS && pObject->IsMoveable() ) + { + Vector lastVel; + lastVel.Init(); + if ( pObject->GetShadowController() ) + { + pObject->GetShadowController()->GetLastImpulse( &lastVel ); + } + else + { + if ( ( pObject->GetCallbackFlags() & CALLBACK_IS_PLAYER_CONTROLLER ) ) + { + CBasePlayer *pPlayer = ToBasePlayer( pInputOwnerEntity ); + IPhysicsPlayerController *pController = pPlayer ? pPlayer->GetPhysicsController() : NULL; + if ( pController ) + { + pController->GetLastImpulse( &lastVel ); + } + } + } + + // Work in progress... + + // Peek into the controller for this object. Look at the input velocity and make sure it's all + // accounted for in the computed stress. If not, redistribute external to internal as it's + // probably being reflected in a way we can't measure here. + float inputLen = lastVel.Length() * (1.0f / physenv->GetSimulationTimestep()) * objMass; + if ( inputLen > 0.0f ) + { + float internalLen = internalForce.Length(); + if ( internalLen < inputLen ) + { + float ratio = internalLen / inputLen; + Vector delta = internalForce * (1.0f - ratio); + internalForce += delta; + float deltaLen = delta.Length(); + sum -= deltaLen; + float extLen = VectorNormalize(externalForce) - deltaLen; + if ( extLen < 0 ) + { + extLen = 0; + } + externalForce *= extLen; + } + } + } + + float invGravity = gravity; + if ( invGravity <= 0 ) + { + invGravity = 1.0f; + } + else + { + invGravity = 1.0f / invGravity; + } + sum *= invGravity; + internalForce *= invGravity; + externalForce *= invGravity; + if ( !pObject->IsMoveable() ) + { + // the above algorithm will see almost all force as internal if the object is not moveable + // (it doesn't push on anything else, so nothing is reciprocated) + // exceptions for friction of a single other object with multiple contact points on this object + + // But the game wants to see it all as external because obviously the object can't move, so it can't have + // internal stress + externalForce = internalForce; + internalForce.Init(); + + if ( !pObject->IsStatic() ) + { + sum += objMass; + } + } + else + { + // assume object is at rest + if ( sum > objMass ) + { + sum = objMass + (sum-objMass) * 0.5; + } + } + + if ( pOutput ) + { + pOutput->exertedStress = internalForce.Length(); + pOutput->receivedStress = externalForce.Length(); + pOutput->hasNonStaticStress = pObjectList.Count() > 2 ? true : false; + pOutput->hasLargeObjectContact = hasLargeObject; + } + + // sum is now kg + return sum; +} -- cgit v1.2.3