//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// #include "cbase.h" #include "bone_setup.h" #include "physics_bone_follower.h" #include "vcollide_parse.h" #include "saverestore_utlvector.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" BEGIN_SIMPLE_DATADESC( physfollower_t ) DEFINE_FIELD( boneIndex, FIELD_INTEGER ), DEFINE_FIELD( hFollower, FIELD_EHANDLE ), END_DATADESC() BEGIN_SIMPLE_DATADESC( CBoneFollowerManager ) DEFINE_GLOBAL_FIELD( m_iNumBones, FIELD_INTEGER ), DEFINE_GLOBAL_UTLVECTOR( m_physBones, FIELD_EMBEDDED ), END_DATADESC() //================================================================================================================ // BONE FOLLOWER MANAGER //================================================================================================================ CBoneFollowerManager::CBoneFollowerManager() { m_iNumBones = 0; } CBoneFollowerManager::~CBoneFollowerManager() { // if this fires then someone isn't destroying their bonefollowers in UpdateOnRemove Assert(m_iNumBones==0); DestroyBoneFollowers(); } //----------------------------------------------------------------------------- // Purpose: // Input : *pEntity - // iNumBones - // **pFollowerBoneNames - //----------------------------------------------------------------------------- void CBoneFollowerManager::InitBoneFollowers( CBaseAnimating *pParentEntity, int iNumBones, const char **pFollowerBoneNames ) { m_iNumBones = iNumBones; m_physBones.EnsureCount( iNumBones ); // Now init all the bones for ( int i = 0; i < iNumBones; i++ ) { CreatePhysicsFollower( pParentEntity, m_physBones[i], pFollowerBoneNames[i], NULL ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBoneFollowerManager::AddBoneFollower( CBaseAnimating *pParentEntity, const char *pFollowerBoneName, solid_t *pSolid ) { m_iNumBones++; int iIndex = m_physBones.AddToTail(); CreatePhysicsFollower( pParentEntity, m_physBones[iIndex], pFollowerBoneName, pSolid ); } // walk the hitboxes and find the first one that is attached to the physics bone in question // return the hitgroup of that box static int HitGroupFromPhysicsBone( CBaseAnimating *pAnim, int physicsBone ) { CStudioHdr *pStudioHdr = pAnim->GetModelPtr( ); mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( pAnim->m_nHitboxSet ); for ( int i = 0; i < set->numhitboxes; i++ ) { if ( pStudioHdr->pBone( set->pHitbox(i)->bone )->physicsbone == physicsBone ) { return set->pHitbox(i)->group; } } return 0; } //----------------------------------------------------------------------------- // Purpose: // Input : &follow - // *pBoneName - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CBoneFollowerManager::CreatePhysicsFollower( CBaseAnimating *pParentEntity, physfollower_t &follow, const char *pBoneName, solid_t *pSolid ) { CStudioHdr *pStudioHdr = pParentEntity->GetModelPtr(); matrix3x4_t boneToWorld; solid_t solidTmp; Vector bonePosition; QAngle boneAngles; int boneIndex = Studio_BoneIndexByName( pStudioHdr, pBoneName ); if ( boneIndex >= 0 ) { mstudiobone_t *pBone = pStudioHdr->pBone( boneIndex ); int physicsBone = pBone->physicsbone; if ( !pSolid ) { if ( !PhysModelParseSolidByIndex( solidTmp, pParentEntity, pParentEntity->GetModelIndex(), physicsBone ) ) return false; pSolid = &solidTmp; } // fixup in case ragdoll is assigned to a parent of the requested follower bone follow.boneIndex = Studio_BoneIndexByName( pStudioHdr, pSolid->name ); if ( follow.boneIndex < 0 ) { follow.boneIndex = boneIndex; } pParentEntity->GetBoneTransform( follow.boneIndex, boneToWorld ); MatrixAngles( boneToWorld, boneAngles, bonePosition ); follow.hFollower = CBoneFollower::Create( pParentEntity, STRING(pParentEntity->GetModelName()), *pSolid, bonePosition, boneAngles ); follow.hFollower->SetTraceData( physicsBone, HitGroupFromPhysicsBone( pParentEntity, physicsBone ) ); follow.hFollower->SetBlocksLOS( pParentEntity->BlocksLOS() ); return true; } else { Warning( "ERROR: Tried to create bone follower on invalid bone %s\n", pBoneName ); } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBoneFollowerManager::UpdateBoneFollowers( CBaseAnimating *pParentEntity ) { if ( m_iNumBones ) { matrix3x4_t boneToWorld; Vector bonePosition; QAngle boneAngles; for ( int i = 0; i < m_iNumBones; i++ ) { if ( !m_physBones[i].hFollower ) continue; pParentEntity->GetBoneTransform( m_physBones[i].boneIndex, boneToWorld ); MatrixAngles( boneToWorld, boneAngles, bonePosition ); m_physBones[i].hFollower->UpdateFollower( bonePosition, boneAngles, 0.1 ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBoneFollowerManager::DestroyBoneFollowers( void ) { for ( int i = 0; i < m_iNumBones; i++ ) { if ( !m_physBones[i].hFollower ) continue; UTIL_Remove( m_physBones[i].hFollower ); m_physBones[i].hFollower = NULL; } m_physBones.Purge(); m_iNumBones = 0; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- physfollower_t *CBoneFollowerManager::GetBoneFollower( int iFollowerIndex ) { Assert( iFollowerIndex >= 0 && iFollowerIndex < m_iNumBones ); if ( iFollowerIndex >= 0 && iFollowerIndex < m_iNumBones ) return &m_physBones[iFollowerIndex]; return NULL; } //----------------------------------------------------------------------------- // Purpose: Retrieve the index for a supplied bone follower // Input : *pFollower - Bone follower to look up // Output : -1 if not found, otherwise the index of the bone follower //----------------------------------------------------------------------------- int CBoneFollowerManager::GetBoneFollowerIndex( CBoneFollower *pFollower ) { if ( pFollower == NULL ) return -1; for ( int i = 0; i < m_iNumBones; i++ ) { if ( !m_physBones[i].hFollower ) continue; if ( m_physBones[i].hFollower == pFollower ) return i; } return -1; } //================================================================================================================ // BONE FOLLOWER //================================================================================================================ //--------------------------------------------------------- // Save/Restore //--------------------------------------------------------- BEGIN_DATADESC( CBoneFollower ) DEFINE_FIELD( m_modelIndex, FIELD_MODELINDEX ), DEFINE_FIELD( m_solidIndex, FIELD_INTEGER ), DEFINE_FIELD( m_physicsBone, FIELD_INTEGER ), DEFINE_FIELD( m_hitGroup, FIELD_INTEGER ), END_DATADESC() IMPLEMENT_SERVERCLASS_ST( CBoneFollower, DT_BoneFollower ) SendPropModelIndex(SENDINFO(m_modelIndex)), SendPropInt(SENDINFO(m_solidIndex), 6, SPROP_UNSIGNED ), END_SEND_TABLE() bool CBoneFollower::Init( CBaseEntity *pOwner, const char *pModelName, solid_t &solid, const Vector &position, const QAngle &orientation ) { SetOwnerEntity( pOwner ); UTIL_SetModel( this, pModelName ); AddEffects( EF_NODRAW ); // invisible m_modelIndex = modelinfo->GetModelIndex( pModelName ); m_solidIndex = solid.index; SetAbsOrigin( position ); SetAbsAngles( orientation ); SetMoveType( MOVETYPE_PUSH ); SetSolid( SOLID_VPHYSICS ); SetCollisionGroup( pOwner->GetCollisionGroup() ); AddSolidFlags( FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST ); solid.params.pGameData = (void *)this; IPhysicsObject *pPhysics = VPhysicsInitShadow( false, false, &solid ); if ( !pPhysics ) return false; // we can't use the default model bounds because each entity is only one bone of the model // so compute the OBB of the physics model and use that. Vector mins, maxs; physcollision->CollideGetAABB( &mins, &maxs, pPhysics->GetCollide(), vec3_origin, vec3_angle ); SetCollisionBounds( mins, maxs ); pPhysics->SetCallbackFlags( pPhysics->GetCallbackFlags() | CALLBACK_GLOBAL_TOUCH ); pPhysics->EnableGravity( false ); // This is not a normal shadow controller that is trying to go to a space occupied by an entity in the game physics // This entity is not running PhysicsPusher(), so Vphysics is supposed to move it // This line of code informs vphysics of that fact if ( pOwner->IsNPC() ) { pPhysics->GetShadowController()->SetPhysicallyControlled( true ); } return true; } int CBoneFollower::UpdateTransmitState() { // Send to the client for client-side collisions and visualization return SetTransmitState( FL_EDICT_PVSCHECK ); } void CBoneFollower::VPhysicsUpdate( IPhysicsObject *pPhysics ) { Vector origin; QAngle angles; pPhysics->GetPosition( &origin, &angles ); SetAbsOrigin( origin ); SetAbsAngles( angles ); } // a little helper class to temporarily change the physics object // for an entity - and change it back when it goes out of scope. class CPhysicsSwapTemp { public: CPhysicsSwapTemp( CBaseEntity *pEntity, IPhysicsObject *pTmpPhysics ) { Assert(pEntity); Assert(pTmpPhysics); m_pEntity = pEntity; m_pPhysics = m_pEntity->VPhysicsGetObject(); if ( m_pPhysics ) { m_pEntity->VPhysicsSwapObject( pTmpPhysics ); } else { m_pEntity->VPhysicsSetObject( pTmpPhysics ); } } ~CPhysicsSwapTemp() { m_pEntity->VPhysicsSwapObject( m_pPhysics ); } private: CBaseEntity *m_pEntity; IPhysicsObject *m_pPhysics; }; void CBoneFollower::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) { CBaseEntity *pOwner = GetOwnerEntity(); if ( pOwner ) { CPhysicsSwapTemp tmp(pOwner, pEvent->pObjects[index] ); pOwner->VPhysicsCollision( index, pEvent ); } } void CBoneFollower::VPhysicsShadowCollision( int index, gamevcollisionevent_t *pEvent ) { CBaseEntity *pOwner = GetOwnerEntity(); if ( pOwner ) { CPhysicsSwapTemp tmp(pOwner, pEvent->pObjects[index] ); pOwner->VPhysicsShadowCollision( index, pEvent ); } } void CBoneFollower::VPhysicsFriction( IPhysicsObject *pObject, float energy, int surfaceProps, int surfacePropsHit ) { CBaseEntity *pOwner = GetOwnerEntity(); if ( pOwner ) { CPhysicsSwapTemp tmp(pOwner, pObject ); pOwner->VPhysicsFriction( pObject, energy, surfaceProps, surfacePropsHit ); } } bool CBoneFollower::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace ) { vcollide_t *pCollide = modelinfo->GetVCollide( GetModelIndex() ); Assert( pCollide && pCollide->solidCount > m_solidIndex ); UTIL_ClearTrace( trace ); physcollision->TraceBox( ray, pCollide->solids[m_solidIndex], GetAbsOrigin(), GetAbsAngles(), &trace ); if ( trace.fraction >= 1 ) return false; // return owner as trace hit trace.m_pEnt = GetOwnerEntity(); trace.hitgroup = m_hitGroup; trace.physicsbone = m_physicsBone; return true; } void CBoneFollower::UpdateFollower( const Vector &position, const QAngle &orientation, float flInterval ) { // UNDONE: Shadow update needs timing info? VPhysicsGetObject()->UpdateShadow( position, orientation, false, flInterval ); } void CBoneFollower::SetTraceData( int physicsBone, int hitGroup ) { m_hitGroup = hitGroup; m_physicsBone = physicsBone; } CBoneFollower *CBoneFollower::Create( CBaseEntity *pOwner, const char *pModelName, solid_t &solid, const Vector &position, const QAngle &orientation ) { CBoneFollower *pFollower = (CBoneFollower *)CreateEntityByName( "phys_bone_follower" ); if ( pFollower ) { pFollower->Init( pOwner, pModelName, solid, position, orientation ); } return pFollower; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CBoneFollower::ObjectCaps() { CBaseEntity *pOwner = GetOwnerEntity(); if ( pOwner ) { if( pOwner->m_iGlobalname != NULL_STRING ) { int caps = BaseClass::ObjectCaps() | pOwner->ObjectCaps(); caps &= ~FCAP_ACROSS_TRANSITION; return caps; } } return BaseClass::ObjectCaps(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBoneFollower::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { CBaseEntity *pOwner = GetOwnerEntity(); if ( pOwner ) { pOwner->Use( pActivator, pCaller, useType, value ); return; } BaseClass::Use( pActivator, pCaller, useType, value ); } //----------------------------------------------------------------------------- // Purpose: Pass on Touch calls to the entity we're following //----------------------------------------------------------------------------- void CBoneFollower::Touch( CBaseEntity *pOther ) { CBaseEntity *pOwner = GetOwnerEntity(); if ( pOwner ) { //TODO: fill in the touch trace with the hitbox number associated with this bone pOwner->Touch( pOther ); return; } BaseClass::Touch( pOther ); } //----------------------------------------------------------------------------- // Purpose: Pass on trace attack calls to the entity we're following //----------------------------------------------------------------------------- void CBoneFollower::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) { CBaseEntity *pOwner = GetOwnerEntity(); if ( pOwner ) { pOwner->DispatchTraceAttack( info, vecDir, ptr, pAccumulator ); return; } BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator ); } LINK_ENTITY_TO_CLASS( phys_bone_follower, CBoneFollower ); // create a manager and a list of followers directly from a ragdoll void CreateBoneFollowersFromRagdoll( CBaseAnimating *pEntity, CBoneFollowerManager *pManager, vcollide_t *pCollide ) { IVPhysicsKeyParser *pParse = physcollision->VPhysicsKeyParserCreate( pCollide->pKeyValues ); while ( !pParse->Finished() ) { const char *pBlock = pParse->GetCurrentBlockName(); if ( !strcmpi( pBlock, "solid" ) ) { solid_t solid; pParse->ParseSolid( &solid, NULL ); // collisions are off by default, turn them on solid.params.enableCollisions = true; solid.params.pName = STRING(pEntity->GetModelName()); pManager->AddBoneFollower( pEntity, solid.name, &solid ); } else { pParse->SkipBlock(); } } }