Kamay Xutax
854991ab8c
The problem is that we still trust the client, although now we have a good base to start with; The key difference here is that we don't need to use anymore the cs player animestate client side anymore because server side values are used There are minor bugs like fire effect but they can be fixed
3592 lines
102 KiB
C++
3592 lines
102 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: Base class for all animating characters and objects.
|
|
//
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "baseanimating.h"
|
|
#include "animation.h"
|
|
#include "activitylist.h"
|
|
#include "studio.h"
|
|
#include "bone_setup.h"
|
|
#include "mathlib/mathlib.h"
|
|
#include "model_types.h"
|
|
#include "datacache/imdlcache.h"
|
|
#include "physics.h"
|
|
#include "ndebugoverlay.h"
|
|
#include "tier1/strtools.h"
|
|
#include "npcevent.h"
|
|
#include "isaverestore.h"
|
|
#include "KeyValues.h"
|
|
#include "tier0/vprof.h"
|
|
#include "EntityFlame.h"
|
|
#include "EntityDissolve.h"
|
|
#include "ai_basenpc.h"
|
|
#include "physics_prop_ragdoll.h"
|
|
#include "datacache/idatacache.h"
|
|
#include "smoke_trail.h"
|
|
#include "props.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
ConVar ai_sequence_debug( "ai_sequence_debug", "0" );
|
|
|
|
class CIKSaveRestoreOps : public CClassPtrSaveRestoreOps
|
|
{
|
|
// save data type interface
|
|
void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave )
|
|
{
|
|
Assert( fieldInfo.pTypeDesc->fieldSize == 1 );
|
|
CIKContext **pIK = (CIKContext **)fieldInfo.pField;
|
|
bool bHasIK = (*pIK) != 0;
|
|
pSave->WriteBool( &bHasIK );
|
|
}
|
|
|
|
void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore )
|
|
{
|
|
Assert( fieldInfo.pTypeDesc->fieldSize == 1 );
|
|
CIKContext **pIK = (CIKContext **)fieldInfo.pField;
|
|
|
|
bool bHasIK;
|
|
pRestore->ReadBool( &bHasIK );
|
|
*pIK = (bHasIK) ? new CIKContext : NULL;
|
|
}
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Relative lighting entity
|
|
//-----------------------------------------------------------------------------
|
|
class CInfoLightingRelative : public CBaseEntity
|
|
{
|
|
public:
|
|
DECLARE_CLASS( CInfoLightingRelative, CBaseEntity );
|
|
DECLARE_DATADESC();
|
|
DECLARE_SERVERCLASS();
|
|
|
|
virtual void Activate();
|
|
virtual void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways );
|
|
virtual int UpdateTransmitState( void );
|
|
|
|
private:
|
|
CNetworkHandle( CBaseEntity, m_hLightingLandmark );
|
|
string_t m_strLightingLandmark;
|
|
};
|
|
|
|
LINK_ENTITY_TO_CLASS( info_lighting_relative, CInfoLightingRelative );
|
|
|
|
BEGIN_DATADESC( CInfoLightingRelative )
|
|
DEFINE_KEYFIELD( m_strLightingLandmark, FIELD_STRING, "LightingLandmark" ),
|
|
DEFINE_FIELD( m_hLightingLandmark, FIELD_EHANDLE ),
|
|
END_DATADESC()
|
|
|
|
IMPLEMENT_SERVERCLASS_ST(CInfoLightingRelative, DT_InfoLightingRelative)
|
|
SendPropEHandle( SENDINFO( m_hLightingLandmark ) ),
|
|
END_SEND_TABLE()
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Activate!
|
|
//-----------------------------------------------------------------------------
|
|
void CInfoLightingRelative::Activate()
|
|
{
|
|
BaseClass::Activate();
|
|
if ( m_strLightingLandmark == NULL_STRING )
|
|
{
|
|
m_hLightingLandmark = NULL;
|
|
}
|
|
else
|
|
{
|
|
m_hLightingLandmark = gEntList.FindEntityByName( NULL, m_strLightingLandmark );
|
|
if ( !m_hLightingLandmark )
|
|
{
|
|
DevWarning( "%s: Could not find lighting landmark '%s'!\n", GetClassname(), STRING( m_strLightingLandmark ) );
|
|
}
|
|
else
|
|
{
|
|
// Set a force transmit because we do not have a model.
|
|
m_hLightingLandmark->AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Force our lighting landmark to be transmitted
|
|
//-----------------------------------------------------------------------------
|
|
void CInfoLightingRelative::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
|
|
{
|
|
// Are we already marked for transmission?
|
|
if ( pInfo->m_pTransmitEdict->Get( entindex() ) )
|
|
return;
|
|
|
|
BaseClass::SetTransmit( pInfo, bAlways );
|
|
|
|
// Force our constraint entity to be sent too.
|
|
if ( m_hLightingLandmark )
|
|
{
|
|
if ( m_hLightingLandmark->GetMoveParent() )
|
|
{
|
|
// Set a full check because we have a move parent.
|
|
m_hLightingLandmark->SetTransmitState( FL_EDICT_FULLCHECK );
|
|
}
|
|
else
|
|
{
|
|
m_hLightingLandmark->SetTransmitState( FL_EDICT_ALWAYS );
|
|
}
|
|
|
|
m_hLightingLandmark->SetTransmit( pInfo, bAlways );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose Force our lighting landmark to be transmitted
|
|
//-----------------------------------------------------------------------------
|
|
int CInfoLightingRelative::UpdateTransmitState( void )
|
|
{
|
|
return SetTransmitState( FL_EDICT_ALWAYS );
|
|
}
|
|
|
|
static CIKSaveRestoreOps s_IKSaveRestoreOp;
|
|
|
|
|
|
BEGIN_DATADESC( CBaseAnimating )
|
|
|
|
DEFINE_FIELD( m_flGroundSpeed, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_flLastEventCheck, FIELD_TIME ),
|
|
DEFINE_FIELD( m_bSequenceFinished, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bSequenceLoops, FIELD_BOOLEAN ),
|
|
|
|
// DEFINE_FIELD( m_nForceBone, FIELD_INTEGER ),
|
|
// DEFINE_FIELD( m_vecForce, FIELD_VECTOR ),
|
|
|
|
DEFINE_INPUT( m_nSkin, FIELD_INTEGER, "skin" ),
|
|
DEFINE_KEYFIELD( m_nBody, FIELD_INTEGER, "body" ),
|
|
DEFINE_INPUT( m_nBody, FIELD_INTEGER, "SetBodyGroup" ),
|
|
DEFINE_KEYFIELD( m_nHitboxSet, FIELD_INTEGER, "hitboxset" ),
|
|
DEFINE_KEYFIELD( m_nSequence, FIELD_INTEGER, "sequence" ),
|
|
DEFINE_ARRAY( m_flPoseParameter, FIELD_FLOAT, CBaseAnimating::NUM_POSEPAREMETERS ),
|
|
DEFINE_ARRAY( m_flEncodedController, FIELD_FLOAT, CBaseAnimating::NUM_BONECTRLS ),
|
|
DEFINE_KEYFIELD( m_flPlaybackRate, FIELD_FLOAT, "playbackrate" ),
|
|
DEFINE_KEYFIELD( m_flCycle, FIELD_FLOAT, "cycle" ),
|
|
// DEFINE_FIELD( m_flIKGroundContactTime, FIELD_TIME ),
|
|
// DEFINE_FIELD( m_flIKGroundMinHeight, FIELD_FLOAT ),
|
|
// DEFINE_FIELD( m_flIKGroundMaxHeight, FIELD_FLOAT ),
|
|
// DEFINE_FIELD( m_flEstIkFloor, FIELD_FLOAT ),
|
|
// DEFINE_FIELD( m_flEstIkOffset, FIELD_FLOAT ),
|
|
// DEFINE_FIELD( m_pStudioHdr, CStudioHdr ),
|
|
// DEFINE_FIELD( m_StudioHdrInitLock, CThreadFastMutex ),
|
|
// DEFINE_FIELD( m_BoneSetupMutex, CThreadFastMutex ),
|
|
DEFINE_CUSTOM_FIELD( m_pIk, &s_IKSaveRestoreOp ),
|
|
DEFINE_FIELD( m_iIKCounter, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_bClientSideAnimation, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bClientSideFrameReset, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_nNewSequenceParity, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_nResetEventsParity, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_nMuzzleFlashParity, FIELD_CHARACTER ),
|
|
|
|
DEFINE_KEYFIELD( m_iszLightingOriginRelative, FIELD_STRING, "LightingOriginHack" ),
|
|
DEFINE_KEYFIELD( m_iszLightingOrigin, FIELD_STRING, "LightingOrigin" ),
|
|
|
|
DEFINE_FIELD( m_hLightingOrigin, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_hLightingOriginRelative, FIELD_EHANDLE ),
|
|
|
|
DEFINE_FIELD( m_flModelScale, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_flDissolveStartTime, FIELD_TIME ),
|
|
|
|
// DEFINE_FIELD( m_boneCacheHandle, memhandle_t ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Ignite", InputIgnite ),
|
|
DEFINE_INPUTFUNC( FIELD_FLOAT, "IgniteLifetime", InputIgniteLifetime ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "IgniteNumHitboxFires", InputIgniteNumHitboxFires ),
|
|
DEFINE_INPUTFUNC( FIELD_FLOAT, "IgniteHitboxFireScale", InputIgniteHitboxFireScale ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "BecomeRagdoll", InputBecomeRagdoll ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetLightingOriginHack", InputSetLightingOriginRelative ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetLightingOrigin", InputSetLightingOrigin ),
|
|
DEFINE_OUTPUT( m_OnIgnite, "OnIgnite" ),
|
|
|
|
DEFINE_INPUT( m_fadeMinDist, FIELD_FLOAT, "fademindist" ),
|
|
DEFINE_INPUT( m_fadeMaxDist, FIELD_FLOAT, "fademaxdist" ),
|
|
DEFINE_KEYFIELD( m_flFadeScale, FIELD_FLOAT, "fadescale" ),
|
|
|
|
DEFINE_KEYFIELD( m_flModelScale, FIELD_FLOAT, "modelscale" ),
|
|
DEFINE_INPUTFUNC( FIELD_VECTOR, "SetModelScale", InputSetModelScale ),
|
|
|
|
DEFINE_FIELD( m_fBoneCacheFlags, FIELD_SHORT ),
|
|
|
|
END_DATADESC()
|
|
|
|
void *SendProxy_ClientSideAnimation( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID );
|
|
|
|
// SendTable stuff.
|
|
IMPLEMENT_SERVERCLASS_ST(CBaseAnimating, DT_BaseAnimating)
|
|
SendPropInt ( SENDINFO(m_nForceBone), 8, 0 ),
|
|
SendPropVector ( SENDINFO(m_vecForce), -1, SPROP_NOSCALE ),
|
|
|
|
SendPropInt ( SENDINFO(m_nSkin), ANIMATION_SKIN_BITS),
|
|
SendPropInt ( SENDINFO(m_nBody), ANIMATION_BODY_BITS),
|
|
|
|
SendPropInt ( SENDINFO(m_nHitboxSet),ANIMATION_HITBOXSET_BITS, SPROP_UNSIGNED ),
|
|
|
|
SendPropFloat ( SENDINFO(m_flModelScale) ),
|
|
|
|
SendPropArray3 ( SENDINFO_ARRAY3(m_flPoseParameter), SendPropFloat(SENDINFO_ARRAY(m_flPoseParameter), ANIMATION_POSEPARAMETER_BITS, 0, 0.0f, 1.0f ) ),
|
|
|
|
SendPropInt ( SENDINFO(m_nSequence), ANIMATION_SEQUENCE_BITS, SPROP_UNSIGNED ),
|
|
SendPropFloat ( SENDINFO(m_flPlaybackRate), ANIMATION_PLAYBACKRATE_BITS, SPROP_ROUNDUP, -4.0, 12.0f ), // NOTE: if this isn't a power of 2 than "1.0" can't be encoded correctly
|
|
|
|
SendPropArray3 (SENDINFO_ARRAY3(m_flEncodedController), SendPropFloat(SENDINFO_ARRAY(m_flEncodedController), 11, SPROP_ROUNDDOWN, 0.0f, 1.0f ) ),
|
|
|
|
SendPropInt( SENDINFO( m_bClientSideAnimation ), 1, SPROP_UNSIGNED ),
|
|
SendPropInt( SENDINFO( m_bClientSideFrameReset ), 1, SPROP_UNSIGNED ),
|
|
|
|
SendPropInt( SENDINFO( m_nNewSequenceParity ), EF_PARITY_BITS, SPROP_UNSIGNED ),
|
|
SendPropInt( SENDINFO( m_nResetEventsParity ), EF_PARITY_BITS, SPROP_UNSIGNED ),
|
|
SendPropInt( SENDINFO( m_nMuzzleFlashParity ), EF_MUZZLEFLASH_BITS, SPROP_UNSIGNED ),
|
|
|
|
SendPropEHandle( SENDINFO( m_hLightingOrigin ) ),
|
|
SendPropEHandle( SENDINFO( m_hLightingOriginRelative ) ),
|
|
|
|
SendPropFloat( SENDINFO(m_flCycle) ),
|
|
|
|
// Fading
|
|
SendPropFloat( SENDINFO( m_fadeMinDist ), 0, SPROP_NOSCALE ),
|
|
SendPropFloat( SENDINFO( m_fadeMaxDist ), 0, SPROP_NOSCALE ),
|
|
SendPropFloat( SENDINFO( m_flFadeScale ), 0, SPROP_NOSCALE ),
|
|
|
|
END_SEND_TABLE()
|
|
|
|
|
|
CBaseAnimating::CBaseAnimating()
|
|
{
|
|
m_vecForce.GetForModify().Init();
|
|
m_nForceBone = 0;
|
|
|
|
m_bResetSequenceInfoOnLoad = false;
|
|
m_bClientSideAnimation = false;
|
|
m_pIk = NULL;
|
|
m_iIKCounter = 0;
|
|
|
|
InitStepHeightAdjust();
|
|
|
|
m_flModelScale = 1.0f;
|
|
// initialize anim clock
|
|
m_flAnimTime = gpGlobals->curtime;
|
|
m_flPrevAnimTime = gpGlobals->curtime;
|
|
m_nNewSequenceParity = 0;
|
|
m_nResetEventsParity = 0;
|
|
m_boneCacheHandle = 0;
|
|
m_pStudioHdr = NULL;
|
|
m_fadeMinDist = 0;
|
|
m_fadeMaxDist = 0;
|
|
m_flFadeScale = 0.0f;
|
|
m_fBoneCacheFlags = 0;
|
|
}
|
|
|
|
CBaseAnimating::~CBaseAnimating()
|
|
{
|
|
Studio_DestroyBoneCache( m_boneCacheHandle );
|
|
delete m_pIk;
|
|
UnlockStudioHdr();
|
|
delete m_pStudioHdr;
|
|
}
|
|
|
|
void CBaseAnimating::Precache()
|
|
{
|
|
#if !defined( TF_DLL )
|
|
// Anything derived from this class can potentially burn - true, but do we want it to!
|
|
PrecacheParticleSystem( "burning_character" );
|
|
#endif
|
|
|
|
BaseClass::Precache();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Activate!
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::Activate()
|
|
{
|
|
BaseClass::Activate();
|
|
SetLightingOrigin( m_iszLightingOrigin );
|
|
SetLightingOriginRelative( m_iszLightingOriginRelative );
|
|
|
|
// Scaled physics objects (re)create their physics here
|
|
if ( m_flModelScale != 1.0f && VPhysicsGetObject() )
|
|
{
|
|
// sanity check to make sure 'm_flModelScale' is in sync with the
|
|
Assert( m_flModelScale > 0.0f );
|
|
|
|
UTIL_CreateScaledPhysObject( this, m_flModelScale );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Force our lighting origin to be trasmitted
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
|
|
{
|
|
// Are we already marked for transmission?
|
|
if ( pInfo->m_pTransmitEdict->Get( entindex() ) )
|
|
return;
|
|
|
|
BaseClass::SetTransmit( pInfo, bAlways );
|
|
|
|
// Force our lighting entities to be sent too.
|
|
if ( m_hLightingOrigin )
|
|
{
|
|
m_hLightingOrigin->SetTransmit( pInfo, bAlways );
|
|
}
|
|
if ( m_hLightingOriginRelative )
|
|
{
|
|
m_hLightingOriginRelative->SetTransmit( pInfo, bAlways );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseAnimating::Restore( IRestore &restore )
|
|
{
|
|
int result = BaseClass::Restore( restore );
|
|
if ( m_flModelScale <= 0.0f )
|
|
m_flModelScale = 1.0f;
|
|
LockStudioHdr();
|
|
return result;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::OnRestore()
|
|
{
|
|
BaseClass::OnRestore();
|
|
|
|
if ( m_nSequence != -1 && GetModelPtr() && !IsValidSequence( m_nSequence ) )
|
|
m_nSequence = 0;
|
|
|
|
m_flEstIkFloor = GetLocalOrigin().z;
|
|
PopulatePoseParameters();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::Spawn()
|
|
{
|
|
BaseClass::Spawn();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::UseClientSideAnimation()
|
|
{
|
|
m_bClientSideAnimation = true;
|
|
}
|
|
|
|
#define MAX_ANIMTIME_INTERVAL 0.2f
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : float
|
|
//-----------------------------------------------------------------------------
|
|
float CBaseAnimating::GetAnimTimeInterval( void ) const
|
|
{
|
|
float flInterval;
|
|
if (m_flAnimTime < gpGlobals->curtime)
|
|
{
|
|
// estimate what it'll be this frame
|
|
flInterval = clamp( gpGlobals->curtime - m_flAnimTime, 0.f, MAX_ANIMTIME_INTERVAL );
|
|
}
|
|
else
|
|
{
|
|
// report actual
|
|
flInterval = clamp( m_flAnimTime - m_flPrevAnimTime, 0.f, MAX_ANIMTIME_INTERVAL );
|
|
}
|
|
return flInterval;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::StudioFrameAdvanceInternal( CStudioHdr *pStudioHdr, float flCycleDelta )
|
|
{
|
|
float flNewCycle = GetCycle() + flCycleDelta;
|
|
if (flNewCycle < 0.0 || flNewCycle >= 1.0)
|
|
{
|
|
if (m_bSequenceLoops)
|
|
{
|
|
flNewCycle -= (int)(flNewCycle);
|
|
}
|
|
else
|
|
{
|
|
flNewCycle = (flNewCycle < 0.0f) ? 0.0f : 1.0f;
|
|
}
|
|
m_bSequenceFinished = true; // just in case it wasn't caught in GetEvents
|
|
}
|
|
else if (flNewCycle > GetLastVisibleCycle( pStudioHdr, GetSequence() ))
|
|
{
|
|
m_bSequenceFinished = true;
|
|
}
|
|
|
|
SetCycle( flNewCycle );
|
|
|
|
/*
|
|
if (!IsPlayer())
|
|
Msg("%s %6.3f : %6.3f %6.3f (%.3f) %.3f\n",
|
|
GetClassname(), gpGlobals->curtime,
|
|
m_flAnimTime.Get(), m_flPrevAnimTime, flInterval, GetCycle() );
|
|
*/
|
|
|
|
m_flGroundSpeed = GetSequenceGroundSpeed( pStudioHdr, GetSequence() ) * GetModelScale();
|
|
|
|
// Msg("%s : %s : %5.1f\n", GetClassname(), GetSequenceName( GetSequence() ), GetCycle() );
|
|
InvalidatePhysicsRecursive( ANIMATION_CHANGED );
|
|
|
|
InvalidateBoneCacheIfOlderThan( 0 );
|
|
}
|
|
|
|
void CBaseAnimating::InvalidateBoneCacheIfOlderThan( float deltaTime )
|
|
{
|
|
CBoneCache *pcache = Studio_GetBoneCache( m_boneCacheHandle );
|
|
if ( !pcache || !pcache->IsValid( gpGlobals->curtime, deltaTime ) )
|
|
{
|
|
InvalidateBoneCache();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::StudioFrameAdvanceManual( float flInterval )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
if ( !pStudioHdr )
|
|
return;
|
|
|
|
UpdateModelScale();
|
|
m_flAnimTime = gpGlobals->curtime;
|
|
m_flPrevAnimTime = m_flAnimTime - flInterval;
|
|
float flCycleRate = GetSequenceCycleRate( pStudioHdr, GetSequence() ) * m_flPlaybackRate;
|
|
StudioFrameAdvanceInternal( GetModelPtr(), flInterval * flCycleRate );
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
// StudioFrameAdvance - advance the animation frame up some interval (default 0.1) into the future
|
|
//=========================================================
|
|
void CBaseAnimating::StudioFrameAdvance()
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
|
|
if ( !pStudioHdr || !pStudioHdr->SequencesAvailable() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
UpdateModelScale();
|
|
|
|
if ( !m_flPrevAnimTime )
|
|
{
|
|
m_flPrevAnimTime = m_flAnimTime;
|
|
}
|
|
|
|
// Time since last animation
|
|
float flInterval = GetAnimTimeInterval();
|
|
|
|
//Msg( "%i %s interval %f\n", entindex(), GetClassname(), flInterval );
|
|
if (flInterval <= 0.001f)
|
|
{
|
|
// Msg("%s : %s : %5.3f (skip)\n", GetClassname(), GetSequenceName( GetSequence() ), GetCycle() );
|
|
return;
|
|
}
|
|
|
|
// Set current
|
|
m_flAnimTime = gpGlobals->curtime;
|
|
|
|
// Latch prev
|
|
m_flPrevAnimTime = m_flAnimTime - flInterval;
|
|
|
|
// Drive cycle
|
|
float flCycleRate = GetSequenceCycleRate( pStudioHdr, GetSequence() ) * m_flPlaybackRate;
|
|
|
|
StudioFrameAdvanceInternal( pStudioHdr, flInterval * flCycleRate );
|
|
|
|
if (ai_sequence_debug.GetBool() == true && m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)
|
|
{
|
|
Msg("%5.2f : %s : %s : %5.3f\n", gpGlobals->curtime, GetClassname(), GetSequenceName( GetSequence() ), GetCycle() );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Set the relative lighting origin
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::SetLightingOriginRelative( string_t strLightingOriginRelative )
|
|
{
|
|
if ( strLightingOriginRelative == NULL_STRING )
|
|
{
|
|
SetLightingOriginRelative( NULL );
|
|
}
|
|
else
|
|
{
|
|
CBaseEntity *pLightingOrigin = gEntList.FindEntityByName( NULL, strLightingOriginRelative );
|
|
if ( !pLightingOrigin )
|
|
{
|
|
DevWarning( "%s: Could not find info_lighting_relative '%s'!\n", GetClassname(), STRING( strLightingOriginRelative ) );
|
|
return;
|
|
}
|
|
else if ( !dynamic_cast<CInfoLightingRelative *>(pLightingOrigin) )
|
|
{
|
|
if( !pLightingOrigin )
|
|
{
|
|
DevWarning( "%s: Cannot find Lighting Origin named: %s\n", GetEntityName().ToCStr(), STRING(strLightingOriginRelative) );
|
|
}
|
|
else
|
|
{
|
|
DevWarning( "%s: Specified entity '%s' must be a info_lighting_relative!\n",
|
|
pLightingOrigin->GetClassname(), pLightingOrigin->GetEntityName().ToCStr() );
|
|
}
|
|
return;
|
|
}
|
|
|
|
SetLightingOriginRelative( pLightingOrigin );
|
|
}
|
|
|
|
// Save the name so that save/load will correctly restore it in Activate()
|
|
m_iszLightingOriginRelative = strLightingOriginRelative;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Set the lighting origin
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::SetLightingOrigin( string_t strLightingOrigin )
|
|
{
|
|
if ( strLightingOrigin == NULL_STRING )
|
|
{
|
|
SetLightingOrigin( NULL );
|
|
}
|
|
else
|
|
{
|
|
CBaseEntity *pLightingOrigin = gEntList.FindEntityByName( NULL, strLightingOrigin );
|
|
if ( !pLightingOrigin )
|
|
{
|
|
DevWarning( "%s: Could not find lighting origin entity named '%s'!\n", GetClassname(), STRING( strLightingOrigin ) );
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
SetLightingOrigin( pLightingOrigin );
|
|
}
|
|
}
|
|
|
|
// Save the name so that save/load will correctly restore it in Activate()
|
|
m_iszLightingOrigin = strLightingOrigin;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &inputdata -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::InputSetLightingOriginRelative( inputdata_t &inputdata )
|
|
{
|
|
// Find our specified target
|
|
string_t strLightingOriginRelative = MAKE_STRING( inputdata.value.String() );
|
|
SetLightingOriginRelative( strLightingOriginRelative );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &inputdata -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::InputSetLightingOrigin( inputdata_t &inputdata )
|
|
{
|
|
// Find our specified target
|
|
string_t strLightingOrigin = MAKE_STRING( inputdata.value.String() );
|
|
SetLightingOrigin( strLightingOrigin );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: SetModelScale input handler
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::InputSetModelScale( inputdata_t &inputdata )
|
|
{
|
|
Vector vecScale;
|
|
inputdata.value.Vector3D( vecScale );
|
|
|
|
SetModelScale( vecScale.x, vecScale.y );
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
// SelectWeightedSequence
|
|
//=========================================================
|
|
int CBaseAnimating::SelectWeightedSequence ( Activity activity )
|
|
{
|
|
Assert( activity != ACT_INVALID );
|
|
Assert( GetModelPtr() );
|
|
return ::SelectWeightedSequence( GetModelPtr(), activity, GetSequence() );
|
|
}
|
|
|
|
|
|
int CBaseAnimating::SelectWeightedSequence ( Activity activity, int curSequence )
|
|
{
|
|
Assert( activity != ACT_INVALID );
|
|
Assert( GetModelPtr() );
|
|
return ::SelectWeightedSequence( GetModelPtr(), activity, curSequence );
|
|
}
|
|
|
|
//=========================================================
|
|
// ResetActivityIndexes
|
|
//=========================================================
|
|
void CBaseAnimating::ResetActivityIndexes ( void )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
::ResetActivityIndexes( GetModelPtr() );
|
|
}
|
|
|
|
//=========================================================
|
|
// ResetEventIndexes
|
|
//=========================================================
|
|
void CBaseAnimating::ResetEventIndexes ( void )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
::ResetEventIndexes( GetModelPtr() );
|
|
}
|
|
|
|
//=========================================================
|
|
// LookupHeaviestSequence
|
|
//
|
|
// Get sequence with highest 'weight' for this activity
|
|
//
|
|
//=========================================================
|
|
int CBaseAnimating::SelectHeaviestSequence ( Activity activity )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
return ::SelectHeaviestSequence( GetModelPtr(), activity );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Looks up an activity by name.
|
|
// Input : label - Name of the activity, ie "ACT_IDLE".
|
|
// Output : Returns the activity ID or ACT_INVALID.
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseAnimating::LookupActivity( const char *label )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
return ::LookupActivity( GetModelPtr(), label );
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
int CBaseAnimating::LookupSequence( const char *label )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
return ::LookupSequence( GetModelPtr(), label );
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
KeyValues *CBaseAnimating::GetSequenceKeyValues( int iSequence )
|
|
{
|
|
const char *szText = Studio_GetKeyValueText( GetModelPtr(), iSequence );
|
|
|
|
if (szText)
|
|
{
|
|
KeyValues *seqKeyValues = new KeyValues("");
|
|
if ( seqKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), szText ) )
|
|
{
|
|
return seqKeyValues;
|
|
}
|
|
seqKeyValues->deleteThis();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//
|
|
// Input : iSequence -
|
|
//
|
|
// Output : float -
|
|
//-----------------------------------------------------------------------------
|
|
float CBaseAnimating::GetSequenceMoveYaw( int iSequence )
|
|
{
|
|
Vector vecReturn;
|
|
|
|
Assert( GetModelPtr() );
|
|
::GetSequenceLinearMotion( GetModelPtr(), iSequence, GetPoseParameterArray(), &vecReturn );
|
|
|
|
if (vecReturn.Length() > 0)
|
|
{
|
|
return UTIL_VecToYaw( vecReturn );
|
|
}
|
|
|
|
return NOMOTION;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//
|
|
// Input : iSequence -
|
|
//
|
|
// Output : float
|
|
//-----------------------------------------------------------------------------
|
|
float CBaseAnimating::GetSequenceMoveDist( CStudioHdr *pStudioHdr, int iSequence )
|
|
{
|
|
Vector vecReturn;
|
|
|
|
::GetSequenceLinearMotion( pStudioHdr, iSequence, GetPoseParameterArray(), &vecReturn );
|
|
|
|
return vecReturn.Length();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//
|
|
// Input : iSequence -
|
|
// *pVec -
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::GetSequenceLinearMotion( int iSequence, Vector *pVec )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
::GetSequenceLinearMotion( GetModelPtr(), iSequence, GetPoseParameterArray(), pVec );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//
|
|
// Input : iSequence -
|
|
//
|
|
// Output : char
|
|
//-----------------------------------------------------------------------------
|
|
const char *CBaseAnimating::GetSequenceName( int iSequence )
|
|
{
|
|
if( iSequence == -1 )
|
|
{
|
|
return "Not Found!";
|
|
}
|
|
|
|
if ( !GetModelPtr() )
|
|
return "No model!";
|
|
|
|
return ::GetSequenceName( GetModelPtr(), iSequence );
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//
|
|
// Input : iSequence -
|
|
//
|
|
// Output : char
|
|
//-----------------------------------------------------------------------------
|
|
const char *CBaseAnimating::GetSequenceActivityName( int iSequence )
|
|
{
|
|
if( iSequence == -1 )
|
|
{
|
|
return "Not Found!";
|
|
}
|
|
|
|
if ( !GetModelPtr() )
|
|
return "No model!";
|
|
|
|
return ::GetSequenceActivityName( GetModelPtr(), iSequence );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Make this a client-side simulated entity
|
|
// Input : force - vector of force to be exerted in the physics simulation
|
|
// forceBone - bone to exert force upon
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseAnimating::BecomeRagdollOnClient( const Vector &force )
|
|
{
|
|
// If this character has a ragdoll animation, turn it over to the physics system
|
|
if ( CanBecomeRagdoll() )
|
|
{
|
|
VPhysicsDestroyObject();
|
|
AddSolidFlags( FSOLID_NOT_SOLID );
|
|
m_nRenderFX = kRenderFxRagdoll;
|
|
|
|
// Have to do this dance because m_vecForce is a network vector
|
|
// and can't be sent to ClampRagdollForce as a Vector *
|
|
Vector vecClampedForce;
|
|
ClampRagdollForce( force, &vecClampedForce );
|
|
m_vecForce = vecClampedForce;
|
|
|
|
SetParent( NULL );
|
|
|
|
AddFlag( FL_TRANSRAGDOLL );
|
|
|
|
SetMoveType( MOVETYPE_NONE );
|
|
//UTIL_SetSize( this, vec3_origin, vec3_origin );
|
|
SetThink( NULL );
|
|
|
|
SetNextThink( gpGlobals->curtime + 2.0f );
|
|
//If we're here, then we can vanish safely
|
|
SetThink( &CBaseEntity::SUB_Remove );
|
|
|
|
// Remove our flame entity if it's attached to us
|
|
CEntityFlame *pFireChild = dynamic_cast<CEntityFlame *>( GetEffectEntity() );
|
|
if ( pFireChild )
|
|
{
|
|
pFireChild->SetThink( &CBaseEntity::SUB_Remove );
|
|
pFireChild->SetNextThink( gpGlobals->curtime + 0.1f );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CBaseAnimating::IsRagdoll()
|
|
{
|
|
return ( m_nRenderFX == kRenderFxRagdoll ) ? true : false;
|
|
}
|
|
|
|
bool CBaseAnimating::CanBecomeRagdoll( void )
|
|
{
|
|
MDLCACHE_CRITICAL_SECTION();
|
|
int ragdollSequence = SelectWeightedSequence( ACT_DIERAGDOLL );
|
|
|
|
//Can't cause we don't have a ragdoll sequence.
|
|
if ( ragdollSequence == ACTIVITY_NOT_AVAILABLE )
|
|
return false;
|
|
|
|
if ( GetFlags() & FL_TRANSRAGDOLL )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CBaseAnimating::ResetSequenceInfo ( )
|
|
{
|
|
if (GetSequence() == -1)
|
|
{
|
|
// This shouldn't happen. Setting m_nSequence blindly is a horrible coding practice.
|
|
SetSequence( 0 );
|
|
}
|
|
|
|
if ( IsDynamicModelLoading() )
|
|
{
|
|
m_bResetSequenceInfoOnLoad = true;
|
|
return;
|
|
}
|
|
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
m_flGroundSpeed = GetSequenceGroundSpeed( pStudioHdr, GetSequence() ) * GetModelScale();
|
|
m_bSequenceLoops = ((GetSequenceFlags( pStudioHdr, GetSequence() ) & STUDIO_LOOPING) != 0);
|
|
// m_flAnimTime = gpGlobals->time;
|
|
m_flPlaybackRate = 1.0;
|
|
m_bSequenceFinished = false;
|
|
m_flLastEventCheck = 0;
|
|
|
|
m_nNewSequenceParity = ( m_nNewSequenceParity+1 ) & EF_PARITY_MASK;
|
|
m_nResetEventsParity = ( m_nResetEventsParity+1 ) & EF_PARITY_MASK;
|
|
|
|
// FIXME: why is this called here? Nothing should have changed to make this nessesary
|
|
if ( pStudioHdr )
|
|
{
|
|
SetEventIndexForSequence( pStudioHdr->pSeqdesc( GetSequence() ) );
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CBaseAnimating::IsValidSequence( int iSequence )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
CStudioHdr* pstudiohdr = GetModelPtr( );
|
|
if (iSequence < 0 || iSequence >= pstudiohdr->GetNumSeq())
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CBaseAnimating::SetSequence( int nSequence )
|
|
{
|
|
Assert( nSequence == 0 || IsDynamicModelLoading() || ( GetModelPtr( ) && ( nSequence < GetModelPtr( )->GetNumSeq() ) && ( GetModelPtr( )->GetNumSeq() < (1 << ANIMATION_SEQUENCE_BITS) ) ) );
|
|
m_nSequence = nSequence;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
float CBaseAnimating::SequenceDuration( CStudioHdr *pStudioHdr, int iSequence )
|
|
{
|
|
if ( !pStudioHdr )
|
|
{
|
|
DevWarning( 2, "CBaseAnimating::SequenceDuration( %d ) NULL pstudiohdr on %s!\n", iSequence, GetClassname() );
|
|
return 0.1;
|
|
}
|
|
if ( !pStudioHdr->SequencesAvailable() )
|
|
{
|
|
return 0.1;
|
|
}
|
|
if (iSequence >= pStudioHdr->GetNumSeq() || iSequence < 0 )
|
|
{
|
|
DevWarning( 2, "CBaseAnimating::SequenceDuration( %d ) out of range\n", iSequence );
|
|
return 0.1;
|
|
}
|
|
|
|
return Studio_Duration( pStudioHdr, iSequence, GetPoseParameterArray() );
|
|
}
|
|
|
|
float CBaseAnimating::GetSequenceCycleRate( CStudioHdr *pStudioHdr, int iSequence )
|
|
{
|
|
float t = SequenceDuration( pStudioHdr, iSequence );
|
|
|
|
if (t > 0.0f)
|
|
{
|
|
return 1.0f / t;
|
|
}
|
|
else
|
|
{
|
|
return 1.0f / 0.1f;
|
|
}
|
|
}
|
|
|
|
|
|
float CBaseAnimating::GetLastVisibleCycle( CStudioHdr *pStudioHdr, int iSequence )
|
|
{
|
|
if ( !pStudioHdr )
|
|
{
|
|
DevWarning( 2, "CBaseAnimating::LastVisibleCycle( %d ) NULL pstudiohdr on %s!\n", iSequence, GetClassname() );
|
|
return 1.0;
|
|
}
|
|
|
|
if (!(GetSequenceFlags( pStudioHdr, iSequence ) & STUDIO_LOOPING))
|
|
{
|
|
return 1.0f - (pStudioHdr->pSeqdesc( iSequence ).fadeouttime) * GetSequenceCycleRate( iSequence ) * m_flPlaybackRate;
|
|
}
|
|
else
|
|
{
|
|
return 1.0;
|
|
}
|
|
}
|
|
|
|
|
|
float CBaseAnimating::GetSequenceGroundSpeed( CStudioHdr *pStudioHdr, int iSequence )
|
|
{
|
|
float t = SequenceDuration( pStudioHdr, iSequence );
|
|
|
|
if (t > 0)
|
|
{
|
|
return ( GetSequenceMoveDist( pStudioHdr, iSequence ) / t );
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
float CBaseAnimating::GetIdealSpeed( ) const
|
|
{
|
|
return m_flGroundSpeed;
|
|
}
|
|
|
|
float CBaseAnimating::GetIdealAccel( ) const
|
|
{
|
|
// return ideal max velocity change over 1 second.
|
|
// tuned for run-walk range of humans
|
|
return GetIdealSpeed() + 50;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns true if the given sequence has the anim event, false if not.
|
|
// Input : nSequence - sequence number to check
|
|
// nEvent - anim event number to look for
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseAnimating::HasAnimEvent( int nSequence, int nEvent )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr();
|
|
if ( !pstudiohdr )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
animevent_t event;
|
|
|
|
int index = 0;
|
|
while ( ( index = GetAnimationEvent( pstudiohdr, nSequence, &event, 0.0f, 1.0f, index ) ) != 0 )
|
|
{
|
|
if ( event.event == nEvent )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
// DispatchAnimEvents
|
|
//=========================================================
|
|
void CBaseAnimating::DispatchAnimEvents ( CBaseAnimating *eventHandler )
|
|
{
|
|
// don't fire events if the framerate is 0
|
|
if (m_flPlaybackRate == 0.0)
|
|
return;
|
|
|
|
animevent_t event;
|
|
|
|
CStudioHdr *pstudiohdr = GetModelPtr( );
|
|
|
|
if ( !pstudiohdr )
|
|
{
|
|
Assert(!"CBaseAnimating::DispatchAnimEvents: model missing");
|
|
return;
|
|
}
|
|
|
|
if ( !pstudiohdr->SequencesAvailable() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// skip this altogether if there are no events
|
|
if (pstudiohdr->pSeqdesc( GetSequence() ).numevents == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// look from when it last checked to some short time in the future
|
|
float flCycleRate = GetSequenceCycleRate( GetSequence() ) * m_flPlaybackRate;
|
|
float flStart = m_flLastEventCheck;
|
|
float flEnd = GetCycle();
|
|
|
|
if (!m_bSequenceLoops && m_bSequenceFinished)
|
|
{
|
|
flEnd = 1.01f;
|
|
}
|
|
m_flLastEventCheck = flEnd;
|
|
|
|
/*
|
|
if (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)
|
|
{
|
|
Msg( "%s:%s : checking %.2f %.2f (%d)\n", STRING(GetModelName()), pstudiohdr->pSeqdesc( GetSequence() ).pszLabel(), flStart, flEnd, m_bSequenceFinished );
|
|
}
|
|
*/
|
|
|
|
// FIXME: does not handle negative framerates!
|
|
int index = 0;
|
|
while ( (index = GetAnimationEvent( pstudiohdr, GetSequence(), &event, flStart, flEnd, index ) ) != 0 )
|
|
{
|
|
event.pSource = this;
|
|
// calc when this event should happen
|
|
if (flCycleRate > 0.0)
|
|
{
|
|
float flCycle = event.cycle;
|
|
if (flCycle > GetCycle())
|
|
{
|
|
flCycle = flCycle - 1.0;
|
|
}
|
|
event.eventtime = m_flAnimTime + (flCycle - GetCycle()) / flCycleRate + GetAnimTimeInterval();
|
|
}
|
|
|
|
/*
|
|
if (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)
|
|
{
|
|
Msg( "dispatch %i (%i) cycle %f event cycle %f cyclerate %f\n",
|
|
(int)(index - 1),
|
|
(int)event.event,
|
|
(float)GetCycle(),
|
|
(float)event.cycle,
|
|
(float)flCycleRate );
|
|
}
|
|
*/
|
|
eventHandler->HandleAnimEvent( &event );
|
|
|
|
// FAILSAFE:
|
|
// If HandleAnimEvent has somehow reset my internal pointer
|
|
// to CStudioHdr to something other than it was when we entered
|
|
// this function, we will crash on the next call to GetAnimationEvent
|
|
// because pstudiohdr no longer points at something valid.
|
|
// So, catch this case, complain vigorously, and bail out of
|
|
// the loop.
|
|
CStudioHdr *pNowStudioHdr = GetModelPtr();
|
|
if ( pNowStudioHdr != pstudiohdr )
|
|
{
|
|
AssertMsg2(false, "%s has changed its model while processing AnimEvents on sequence %d. Aborting dispatch.\n", GetDebugName(), GetSequence() );
|
|
Warning( "%s has changed its model while processing AnimEvents on sequence %d. Aborting dispatch.\n", GetDebugName(), GetSequence() );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::HandleAnimEvent( animevent_t *pEvent )
|
|
{
|
|
if ((pEvent->type & AE_TYPE_NEWEVENTSYSTEM) && (pEvent->type & AE_TYPE_SERVER))
|
|
{
|
|
if ( pEvent->event == AE_SV_PLAYSOUND )
|
|
{
|
|
EmitSound( pEvent->options );
|
|
return;
|
|
}
|
|
else if ( pEvent->event == AE_RAGDOLL )
|
|
{
|
|
// Convert to ragdoll immediately
|
|
BecomeRagdollOnClient( vec3_origin );
|
|
return;
|
|
}
|
|
#ifdef HL2_EPISODIC
|
|
else if ( pEvent->event == AE_SV_DUSTTRAIL )
|
|
{
|
|
char szAttachment[128];
|
|
float flDuration;
|
|
float flSize;
|
|
if (sscanf( pEvent->options, "%s %f %f", szAttachment, &flDuration, &flSize ) == 3)
|
|
{
|
|
CHandle<DustTrail> hDustTrail;
|
|
|
|
hDustTrail = DustTrail::CreateDustTrail();
|
|
|
|
if( hDustTrail )
|
|
{
|
|
hDustTrail->m_SpawnRate = 4; // Particles per second
|
|
hDustTrail->m_ParticleLifetime = 1.5; // Lifetime of each particle, In seconds
|
|
hDustTrail->m_Color.Init(0.5f, 0.46f, 0.44f);
|
|
hDustTrail->m_StartSize = flSize;
|
|
hDustTrail->m_EndSize = hDustTrail->m_StartSize * 8;
|
|
hDustTrail->m_SpawnRadius = 3; // Each particle randomly offset from the center up to this many units
|
|
hDustTrail->m_MinSpeed = 4; // u/sec
|
|
hDustTrail->m_MaxSpeed = 10; // u/sec
|
|
hDustTrail->m_Opacity = 0.5f;
|
|
hDustTrail->SetLifetime(flDuration); // Lifetime of the spawner, in seconds
|
|
hDustTrail->m_StopEmitTime = gpGlobals->curtime + flDuration;
|
|
hDustTrail->SetParent( this, LookupAttachment( szAttachment ) );
|
|
hDustTrail->SetLocalOrigin( vec3_origin );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DevWarning( 1, "%s unable to parse AE_SV_DUSTTRAIL event \"%s\"\n", STRING( GetModelName() ), pEvent->options );
|
|
}
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Failed to find a handler
|
|
const char *pName = EventList_NameForIndex( pEvent->event );
|
|
if ( pName)
|
|
{
|
|
DevWarning( 1, "Unhandled animation event %s for %s\n", pName, GetClassname() );
|
|
}
|
|
else
|
|
{
|
|
DevWarning( 1, "Unhandled animation event %d for %s\n", pEvent->event, GetClassname() );
|
|
}
|
|
}
|
|
|
|
// SetPoseParamater()
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
float CBaseAnimating::SetPoseParameter( CStudioHdr *pStudioHdr, const char *szName, float flValue )
|
|
{
|
|
int poseParam = LookupPoseParameter( pStudioHdr, szName );
|
|
AssertMsg2(poseParam >= 0, "SetPoseParameter called with invalid argument %s by %s", szName, GetDebugName());
|
|
return SetPoseParameter( pStudioHdr, poseParam, flValue );
|
|
}
|
|
|
|
float CBaseAnimating::SetPoseParameter( CStudioHdr *pStudioHdr, int iParameter, float flValue )
|
|
{
|
|
if ( !pStudioHdr )
|
|
{
|
|
return flValue;
|
|
}
|
|
|
|
if (iParameter >= 0)
|
|
{
|
|
float flNewValue;
|
|
flValue = Studio_SetPoseParameter( pStudioHdr, iParameter, flValue, flNewValue );
|
|
m_flPoseParameter.Set( iParameter, flNewValue );
|
|
}
|
|
|
|
return flValue;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
float CBaseAnimating::GetPoseParameter( const char *szName )
|
|
{
|
|
return GetPoseParameter( LookupPoseParameter( szName ) );
|
|
}
|
|
|
|
float CBaseAnimating::GetPoseParameter( int iParameter )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr( );
|
|
|
|
if ( !pstudiohdr )
|
|
{
|
|
Assert(!"CBaseAnimating::GetPoseParameter: model missing");
|
|
return 0.0;
|
|
}
|
|
|
|
if ( !pstudiohdr->SequencesAvailable() )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (iParameter >= 0)
|
|
{
|
|
return Studio_GetPoseParameter( pstudiohdr, iParameter, m_flPoseParameter[ iParameter ] );
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
bool CBaseAnimating::GetPoseParameterRange( int index, float &minValue, float &maxValue )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
|
|
if (pStudioHdr)
|
|
{
|
|
if (index >= 0 && index < pStudioHdr->GetNumPoseParameters())
|
|
{
|
|
const mstudioposeparamdesc_t &pose = pStudioHdr->pPoseParameter( index );
|
|
minValue = pose.start;
|
|
maxValue = pose.end;
|
|
return true;
|
|
}
|
|
}
|
|
minValue = 0.0f;
|
|
maxValue = 1.0f;
|
|
return false;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
int CBaseAnimating::LookupPoseParameter( CStudioHdr *pStudioHdr, const char *szName )
|
|
{
|
|
if ( !pStudioHdr )
|
|
return 0;
|
|
|
|
if ( !pStudioHdr->SequencesAvailable() )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
for (int i = 0; i < pStudioHdr->GetNumPoseParameters(); i++)
|
|
{
|
|
if (Q_stricmp( pStudioHdr->pPoseParameter( i ).pszName(), szName ) == 0)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
// AssertMsg( 0, UTIL_VarArgs( "poseparameter %s couldn't be mapped!!!\n", szName ) );
|
|
return -1; // Error
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CBaseAnimating::HasPoseParameter( int iSequence, const char *szName )
|
|
{
|
|
int iParameter = LookupPoseParameter( szName );
|
|
if (iParameter == -1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return HasPoseParameter( iSequence, iParameter );
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CBaseAnimating::HasPoseParameter( int iSequence, int iParameter )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr( );
|
|
|
|
if ( !pstudiohdr )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( !pstudiohdr->SequencesAvailable() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (iSequence < 0 || iSequence >= pstudiohdr->GetNumSeq())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
mstudioseqdesc_t &seqdesc = pstudiohdr->pSeqdesc( iSequence );
|
|
if (pstudiohdr->GetSharedPoseParameter( iSequence, seqdesc.paramindex[0] ) == iParameter ||
|
|
pstudiohdr->GetSharedPoseParameter( iSequence, seqdesc.paramindex[1] ) == iParameter)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
// Each class that wants to use pose parameters should populate
|
|
// static variables in this entry point, rather than calling
|
|
// GetPoseParameter(const char*) every time you want to adjust
|
|
// an animation.
|
|
//
|
|
// Make sure to call BaseClass::PopulatePoseParameters() at
|
|
// the *bottom* of your function.
|
|
//=========================================================
|
|
void CBaseAnimating::PopulatePoseParameters( void )
|
|
{
|
|
|
|
}
|
|
|
|
//=========================================================
|
|
// Purpose: from input of 75% to 200% of maximum range, rescale smoothly from 75% to 100%
|
|
//=========================================================
|
|
float CBaseAnimating::EdgeLimitPoseParameter( int iParameter, float flValue, float flBase )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr( );
|
|
if ( !pstudiohdr )
|
|
{
|
|
return flValue;
|
|
}
|
|
|
|
if (iParameter < 0 || iParameter >= pstudiohdr->GetNumPoseParameters())
|
|
{
|
|
return flValue;
|
|
}
|
|
|
|
const mstudioposeparamdesc_t &Pose = pstudiohdr->pPoseParameter( iParameter );
|
|
|
|
if (Pose.loop || Pose.start == Pose.end)
|
|
{
|
|
return flValue;
|
|
}
|
|
|
|
return RangeCompressor( flValue, Pose.start, Pose.end, flBase );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns index number of a given named bone
|
|
// Input : name of a bone
|
|
// Output : Bone index number or -1 if bone not found
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseAnimating::LookupBone( const char *szName )
|
|
{
|
|
const CStudioHdr *pStudioHdr = GetModelPtr();
|
|
Assert( pStudioHdr );
|
|
if ( !pStudioHdr )
|
|
return -1;
|
|
return Studio_BoneIndexByName( pStudioHdr, szName );
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CBaseAnimating::GetBonePosition ( int iBone, Vector &origin, QAngle &angles )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr( );
|
|
if (!pStudioHdr)
|
|
{
|
|
Assert(!"CBaseAnimating::GetBonePosition: model missing");
|
|
return;
|
|
}
|
|
|
|
if (iBone < 0 || iBone >= pStudioHdr->numbones())
|
|
{
|
|
Assert(!"CBaseAnimating::GetBonePosition: invalid bone index");
|
|
return;
|
|
}
|
|
|
|
matrix3x4_t bonetoworld;
|
|
GetBoneTransform( iBone, bonetoworld );
|
|
|
|
MatrixAngles( bonetoworld, angles, origin );
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
|
|
void CBaseAnimating::GetBoneTransform( int iBone, matrix3x4_t &pBoneToWorld )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr( );
|
|
|
|
if (!pStudioHdr)
|
|
{
|
|
Assert(!"CBaseAnimating::GetBoneTransform: model missing");
|
|
return;
|
|
}
|
|
|
|
if (iBone < 0 || iBone >= pStudioHdr->numbones())
|
|
{
|
|
Assert(!"CBaseAnimating::GetBoneTransform: invalid bone index");
|
|
return;
|
|
}
|
|
|
|
CBoneCache *pcache = GetBoneCache( );
|
|
|
|
matrix3x4_t *pmatrix = pcache->GetCachedBone( iBone );
|
|
|
|
if ( !pmatrix )
|
|
{
|
|
MatrixCopy( EntityToWorldTransform(), pBoneToWorld );
|
|
return;
|
|
}
|
|
|
|
Assert( pmatrix );
|
|
|
|
// FIXME
|
|
MatrixCopy( *pmatrix, pBoneToWorld );
|
|
}
|
|
|
|
class CTraceFilterSkipNPCs : public CTraceFilterSimple
|
|
{
|
|
public:
|
|
CTraceFilterSkipNPCs( const IHandleEntity *passentity, int collisionGroup )
|
|
: CTraceFilterSimple( passentity, collisionGroup )
|
|
{
|
|
}
|
|
|
|
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
|
|
{
|
|
if ( CTraceFilterSimple::ShouldHitEntity(pServerEntity, contentsMask) )
|
|
{
|
|
CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity );
|
|
if ( pEntity->IsNPC() )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Receives the clients IK floor position
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CBaseAnimating::SetIKGroundContactInfo( float minHeight, float maxHeight )
|
|
{
|
|
m_flIKGroundContactTime = gpGlobals->curtime;
|
|
m_flIKGroundMinHeight = minHeight;
|
|
m_flIKGroundMaxHeight = maxHeight;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Initializes IK floor position
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CBaseAnimating::InitStepHeightAdjust( void )
|
|
{
|
|
m_flIKGroundContactTime = 0;
|
|
m_flIKGroundMinHeight = 0;
|
|
m_flIKGroundMaxHeight = 0;
|
|
|
|
// FIXME: not safe to call GetAbsOrigin here. Hierarchy might not be set up!
|
|
m_flEstIkFloor = GetAbsOrigin().z;
|
|
m_flEstIkOffset = 0;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Interpolates client IK floor position and drops entity down so that the feet will reach
|
|
//-----------------------------------------------------------------------------
|
|
|
|
ConVar npc_height_adjust( "npc_height_adjust", "1", FCVAR_ARCHIVE, "Enable test mode for ik height adjustment" );
|
|
|
|
void CBaseAnimating::UpdateStepOrigin()
|
|
{
|
|
if (!npc_height_adjust.GetBool())
|
|
{
|
|
m_flEstIkOffset = 0;
|
|
m_flEstIkFloor = GetLocalOrigin().z;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
if (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)
|
|
{
|
|
Msg("%x : %x\n", GetMoveParent(), GetGroundEntity() );
|
|
}
|
|
*/
|
|
|
|
if (m_flIKGroundContactTime > 0.2 && m_flIKGroundContactTime > gpGlobals->curtime - 0.2)
|
|
{
|
|
if ((GetFlags() & (FL_FLY | FL_SWIM)) == 0 && GetMoveParent() == NULL && GetGroundEntity() != NULL && !GetGroundEntity()->IsMoving())
|
|
{
|
|
Vector toAbs = GetAbsOrigin() - GetLocalOrigin();
|
|
if (toAbs.z == 0.0)
|
|
{
|
|
CAI_BaseNPC *pNPC = MyNPCPointer();
|
|
// FIXME: There needs to be a default step height somewhere
|
|
float height = 18.0f;
|
|
if (pNPC)
|
|
{
|
|
height = pNPC->StepHeight();
|
|
}
|
|
|
|
// debounce floor location
|
|
m_flEstIkFloor = m_flEstIkFloor * 0.2 + m_flIKGroundMinHeight * 0.8;
|
|
|
|
// don't let heigth difference between min and max exceed step height
|
|
float bias = clamp( (m_flIKGroundMaxHeight - m_flIKGroundMinHeight) - height, 0.f, height );
|
|
// save off reasonable offset
|
|
m_flEstIkOffset = clamp( m_flEstIkFloor - GetAbsOrigin().z, -height + bias, 0.0f );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// don't use floor offset, decay the value
|
|
m_flEstIkOffset *= 0.5;
|
|
m_flEstIkFloor = GetLocalOrigin().z;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the origin to use for model rendering
|
|
//-----------------------------------------------------------------------------
|
|
|
|
Vector CBaseAnimating::GetStepOrigin( void ) const
|
|
{
|
|
Vector tmp = GetLocalOrigin();
|
|
tmp.z += m_flEstIkOffset;
|
|
return tmp;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the origin to use for model rendering
|
|
//-----------------------------------------------------------------------------
|
|
|
|
QAngle CBaseAnimating::GetStepAngles( void ) const
|
|
{
|
|
// TODO: Add in body lean
|
|
return GetLocalAngles();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Find IK collisions with world
|
|
// Input :
|
|
// Output : fills out m_pIk targets, calcs floor offset for rendering
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CBaseAnimating::CalculateIKLocks( float currentTime )
|
|
{
|
|
if ( m_pIk )
|
|
{
|
|
Ray_t ray;
|
|
CTraceFilterSkipNPCs traceFilter( this, GetCollisionGroup() );
|
|
Vector up;
|
|
GetVectors( NULL, NULL, &up );
|
|
// FIXME: check number of slots?
|
|
for (int i = 0; i < m_pIk->m_target.Count(); i++)
|
|
{
|
|
trace_t trace;
|
|
CIKTarget *pTarget = &m_pIk->m_target[i];
|
|
|
|
if (!pTarget->IsActive())
|
|
continue;
|
|
|
|
switch( pTarget->type )
|
|
{
|
|
case IK_GROUND:
|
|
{
|
|
Vector estGround;
|
|
estGround = (pTarget->est.pos - GetAbsOrigin());
|
|
estGround = estGround - (estGround * up) * up;
|
|
estGround = GetAbsOrigin() + estGround + pTarget->est.floor * up;
|
|
|
|
Vector p1, p2;
|
|
VectorMA( estGround, pTarget->est.height, up, p1 );
|
|
VectorMA( estGround, -pTarget->est.height, up, p2 );
|
|
|
|
float r = MAX(pTarget->est.radius,1);
|
|
|
|
// don't IK to other characters
|
|
ray.Init( p1, p2, Vector(-r,-r,0), Vector(r,r,1) );
|
|
enginetrace->TraceRay( ray, MASK_SOLID, &traceFilter, &trace );
|
|
|
|
/*
|
|
debugoverlay->AddBoxOverlay( p1, Vector(-r,-r,0), Vector(r,r,1), QAngle( 0, 0, 0 ), 255, 0, 0, 0, 1.0f );
|
|
debugoverlay->AddBoxOverlay( trace.endpos, Vector(-r,-r,0), Vector(r,r,1), QAngle( 0, 0, 0 ), 255, 0, 0, 0, 1.0f );
|
|
debugoverlay->AddLineOverlay( p1, trace.endpos, 255, 0, 0, 0, 1.0f );
|
|
*/
|
|
|
|
if (trace.startsolid)
|
|
{
|
|
ray.Init( pTarget->trace.hip, pTarget->est.pos, Vector(-r,-r,0), Vector(r,r,1) );
|
|
|
|
enginetrace->TraceRay( ray, MASK_SOLID, &traceFilter, &trace );
|
|
|
|
p1 = trace.endpos;
|
|
VectorMA( p1, - pTarget->est.height, up, p2 );
|
|
ray.Init( p1, p2, Vector(-r,-r,0), Vector(r,r,1) );
|
|
|
|
enginetrace->TraceRay( ray, MASK_SOLID, &traceFilter, &trace );
|
|
}
|
|
|
|
if (!trace.startsolid)
|
|
{
|
|
if (trace.DidHitWorld())
|
|
{
|
|
pTarget->SetPosWithNormalOffset( trace.endpos, trace.plane.normal );
|
|
pTarget->SetNormal( trace.plane.normal );
|
|
}
|
|
else
|
|
{
|
|
pTarget->SetPos( trace.endpos );
|
|
pTarget->SetAngles( GetAbsAngles() );
|
|
}
|
|
|
|
}
|
|
}
|
|
break;
|
|
case IK_ATTACHMENT:
|
|
{
|
|
// anything on the server?
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Clear out animation states that are invalidated with Teleport
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CBaseAnimating::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity )
|
|
{
|
|
BaseClass::Teleport( newPosition, newAngles, newVelocity );
|
|
if (m_pIk)
|
|
{
|
|
m_pIk->ClearTargets( );
|
|
}
|
|
InitStepHeightAdjust();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: build matrices first from the parent, then from the passed in arrays if the bone doesn't exist on the parent
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CBaseAnimating::BuildMatricesWithBoneMerge(
|
|
const CStudioHdr *pStudioHdr,
|
|
const QAngle& angles,
|
|
const Vector& origin,
|
|
const Vector pos[MAXSTUDIOBONES],
|
|
const Quaternion q[MAXSTUDIOBONES],
|
|
matrix3x4_t bonetoworld[MAXSTUDIOBONES],
|
|
CBaseAnimating *pParent,
|
|
CBoneCache *pParentCache
|
|
)
|
|
{
|
|
CStudioHdr *fhdr = pParent->GetModelPtr();
|
|
mstudiobone_t *pbones = pStudioHdr->pBone( 0 );
|
|
|
|
matrix3x4_t rotationmatrix; // model to world transformation
|
|
AngleMatrix( angles, origin, rotationmatrix);
|
|
|
|
for ( int i=0; i < pStudioHdr->numbones(); i++ )
|
|
{
|
|
// Now find the bone in the parent entity.
|
|
bool merged = false;
|
|
int parentBoneIndex = Studio_BoneIndexByName( fhdr, pbones[i].pszName() );
|
|
if ( parentBoneIndex >= 0 )
|
|
{
|
|
matrix3x4_t *pMat = pParentCache->GetCachedBone( parentBoneIndex );
|
|
if ( pMat )
|
|
{
|
|
MatrixCopy( *pMat, bonetoworld[ i ] );
|
|
merged = true;
|
|
}
|
|
}
|
|
|
|
if ( !merged )
|
|
{
|
|
// If we get down here, then the bone wasn't merged.
|
|
matrix3x4_t bonematrix;
|
|
QuaternionMatrix( q[i], pos[i], bonematrix );
|
|
|
|
if (pbones[i].parent == -1)
|
|
{
|
|
ConcatTransforms (rotationmatrix, bonematrix, bonetoworld[i]);
|
|
}
|
|
else
|
|
{
|
|
ConcatTransforms (bonetoworld[pbones[i].parent], bonematrix, bonetoworld[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ConVar sv_pvsskipanimation( "sv_pvsskipanimation", "0", FCVAR_ARCHIVE, "Skips SetupBones when npc's are outside the PVS" );
|
|
ConVar ai_setupbones_debug( "ai_setupbones_debug", "0", 0, "Shows that bones that are setup every think" );
|
|
|
|
|
|
|
|
|
|
inline bool CBaseAnimating::CanSkipAnimation( void )
|
|
{
|
|
if ( !sv_pvsskipanimation.GetBool() )
|
|
return false;
|
|
|
|
CAI_BaseNPC *pNPC = MyNPCPointer();
|
|
if ( pNPC && !pNPC->HasCondition( COND_IN_PVS ) && ( m_fBoneCacheFlags & (BCF_NO_ANIMATION_SKIP | BCF_IS_IN_SPAWN) ) == false )
|
|
{
|
|
// If we have a player as a child, then we better setup our bones. If we don't,
|
|
// the PVS will be screwy.
|
|
return !DoesHavePlayerChild();
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
void CBaseAnimating::SetupBones( matrix3x4_t *pBoneToWorld, int boneMask )
|
|
{
|
|
AUTO_LOCK( m_BoneSetupMutex );
|
|
|
|
VPROF_BUDGET( "CBaseAnimating::SetupBones", VPROF_BUDGETGROUP_SERVER_ANIM );
|
|
|
|
MDLCACHE_CRITICAL_SECTION();
|
|
|
|
Assert( GetModelPtr() );
|
|
|
|
CStudioHdr *pStudioHdr = GetModelPtr( );
|
|
|
|
if(!pStudioHdr)
|
|
{
|
|
Assert(!"CBaseAnimating::GetSkeleton() without a model");
|
|
return;
|
|
}
|
|
|
|
Assert( !IsEFlagSet( EFL_SETTING_UP_BONES ) );
|
|
|
|
AddEFlags( EFL_SETTING_UP_BONES );
|
|
|
|
Vector pos[MAXSTUDIOBONES];
|
|
Quaternion q[MAXSTUDIOBONES];
|
|
|
|
// adjust hit boxes based on IK driven offset
|
|
Vector adjOrigin = GetAbsOrigin() + Vector( 0, 0, m_flEstIkOffset );
|
|
|
|
if ( CanSkipAnimation() )
|
|
{
|
|
IBoneSetup boneSetup( pStudioHdr, boneMask, GetPoseParameterArray() );
|
|
boneSetup.InitPose( pos, q );
|
|
// Msg( "%.03f : %s:%s not in pvs\n", gpGlobals->curtime, GetClassname(), GetEntityName().ToCStr() );
|
|
}
|
|
else
|
|
{
|
|
if ( m_pIk )
|
|
{
|
|
// FIXME: pass this into Studio_BuildMatrices to skip transforms
|
|
CBoneBitList boneComputed;
|
|
m_iIKCounter++;
|
|
m_pIk->Init( pStudioHdr, GetAbsAngles(), adjOrigin, gpGlobals->curtime, m_iIKCounter, boneMask );
|
|
GetSkeleton( pStudioHdr, pos, q, boneMask );
|
|
|
|
m_pIk->UpdateTargets( pos, q, pBoneToWorld, boneComputed );
|
|
CalculateIKLocks( gpGlobals->curtime );
|
|
m_pIk->SolveDependencies( pos, q, pBoneToWorld, boneComputed );
|
|
}
|
|
else
|
|
{
|
|
// Msg( "%.03f : %s:%s\n", gpGlobals->curtime, GetClassname(), GetEntityName().ToCStr() );
|
|
GetSkeleton( pStudioHdr, pos, q, boneMask );
|
|
}
|
|
}
|
|
|
|
CBaseAnimating *pParent = dynamic_cast< CBaseAnimating* >( GetMoveParent() );
|
|
if ( pParent )
|
|
{
|
|
// We're doing bone merging, so do special stuff here.
|
|
CBoneCache *pParentCache = pParent->GetBoneCache();
|
|
if ( pParentCache )
|
|
{
|
|
BuildMatricesWithBoneMerge(
|
|
pStudioHdr,
|
|
GetAbsAngles(),
|
|
adjOrigin,
|
|
pos,
|
|
q,
|
|
pBoneToWorld,
|
|
pParent,
|
|
pParentCache );
|
|
|
|
RemoveEFlags( EFL_SETTING_UP_BONES );
|
|
if (ai_setupbones_debug.GetBool())
|
|
{
|
|
DrawRawSkeleton( pBoneToWorld, boneMask, true, 0.11 );
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
Studio_BuildMatrices(
|
|
pStudioHdr,
|
|
GetAbsAngles(),
|
|
adjOrigin,
|
|
pos,
|
|
q,
|
|
-1,
|
|
GetModelScale(), // Scaling
|
|
pBoneToWorld,
|
|
boneMask );
|
|
|
|
if (ai_setupbones_debug.GetBool())
|
|
{
|
|
// Msg("%s:%s:%s (%x)\n", GetClassname(), GetDebugName(), STRING(GetModelName()), boneMask );
|
|
DrawRawSkeleton( pBoneToWorld, boneMask, true, 0.11 );
|
|
}
|
|
RemoveEFlags( EFL_SETTING_UP_BONES );
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
int CBaseAnimating::GetNumBones ( void )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr( );
|
|
if(pStudioHdr)
|
|
{
|
|
return pStudioHdr->numbones();
|
|
}
|
|
else
|
|
{
|
|
Assert(!"CBaseAnimating::GetNumBones: model missing");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns index number of a given named attachment
|
|
// Input : name of attachment
|
|
// Output : attachment index number or -1 if attachment not found
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseAnimating::LookupAttachment( const char *szName )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr( );
|
|
if (!pStudioHdr)
|
|
{
|
|
Assert(!"CBaseAnimating::LookupAttachment: model missing");
|
|
return 0;
|
|
}
|
|
|
|
// The +1 is to make attachment indices be 1-based (namely 0 == invalid or unused attachment)
|
|
return Studio_FindAttachment( pStudioHdr, szName ) + 1;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the world location and world angles of an attachment
|
|
// Input : attachment name
|
|
// Output : location and angles
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseAnimating::GetAttachment( const char *szName, Vector &absOrigin, QAngle &absAngles )
|
|
{
|
|
return GetAttachment( LookupAttachment( szName ), absOrigin, absAngles );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the world location and world angles of an attachment
|
|
// Input : attachment index
|
|
// Output : location and angles
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseAnimating::GetAttachment ( int iAttachment, Vector &absOrigin, QAngle &absAngles )
|
|
{
|
|
matrix3x4_t attachmentToWorld;
|
|
|
|
bool bRet = GetAttachment( iAttachment, attachmentToWorld );
|
|
MatrixAngles( attachmentToWorld, absAngles, absOrigin );
|
|
return bRet;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the world location and world angles of an attachment
|
|
// Input : attachment index
|
|
// Output : location and angles
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseAnimating::GetAttachment( int iAttachment, matrix3x4_t &attachmentToWorld )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr( );
|
|
if (!pStudioHdr)
|
|
{
|
|
MatrixCopy(EntityToWorldTransform(), attachmentToWorld);
|
|
AssertOnce(!"CBaseAnimating::GetAttachment: model missing");
|
|
return false;
|
|
}
|
|
|
|
if (iAttachment < 1 || iAttachment > pStudioHdr->GetNumAttachments())
|
|
{
|
|
MatrixCopy(EntityToWorldTransform(), attachmentToWorld);
|
|
// Assert(!"CBaseAnimating::GetAttachment: invalid attachment index");
|
|
return false;
|
|
}
|
|
|
|
const mstudioattachment_t &pattachment = pStudioHdr->pAttachment( iAttachment-1 );
|
|
int iBone = pStudioHdr->GetAttachmentBone( iAttachment-1 );
|
|
|
|
matrix3x4_t bonetoworld;
|
|
GetBoneTransform( iBone, bonetoworld );
|
|
if ( (pattachment.flags & ATTACHMENT_FLAG_WORLD_ALIGN) == 0 )
|
|
{
|
|
ConcatTransforms( bonetoworld, pattachment.local, attachmentToWorld );
|
|
}
|
|
else
|
|
{
|
|
Vector vecLocalBonePos, vecWorldBonePos;
|
|
MatrixGetColumn( pattachment.local, 3, vecLocalBonePos );
|
|
VectorTransform( vecLocalBonePos, bonetoworld, vecWorldBonePos );
|
|
|
|
SetIdentityMatrix( attachmentToWorld );
|
|
MatrixSetColumn( vecWorldBonePos, 3, attachmentToWorld );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// gets the bone for an attachment
|
|
int CBaseAnimating::GetAttachmentBone( int iAttachment )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr( );
|
|
if (!pStudioHdr || iAttachment < 1 || iAttachment > pStudioHdr->GetNumAttachments() )
|
|
{
|
|
AssertOnce(pStudioHdr && "CBaseAnimating::GetAttachment: model missing");
|
|
return 0;
|
|
}
|
|
|
|
return pStudioHdr->GetAttachmentBone( iAttachment-1 );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the world location of an attachment
|
|
// Input : attachment index
|
|
// Output : location and angles
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseAnimating::GetAttachment( const char *szName, Vector &absOrigin, Vector *forward, Vector *right, Vector *up )
|
|
{
|
|
return GetAttachment( LookupAttachment( szName ), absOrigin, forward, right, up );
|
|
}
|
|
|
|
bool CBaseAnimating::GetAttachment( int iAttachment, Vector &absOrigin, Vector *forward, Vector *right, Vector *up )
|
|
{
|
|
matrix3x4_t attachmentToWorld;
|
|
|
|
bool bRet = GetAttachment( iAttachment, attachmentToWorld );
|
|
MatrixPosition( attachmentToWorld, absOrigin );
|
|
if (forward)
|
|
{
|
|
MatrixGetColumn( attachmentToWorld, 0, forward );
|
|
}
|
|
if (right)
|
|
{
|
|
MatrixGetColumn( attachmentToWorld, 1, right );
|
|
}
|
|
if (up)
|
|
{
|
|
MatrixGetColumn( attachmentToWorld, 2, up );
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns the attachment in local space
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseAnimating::GetAttachmentLocal( const char *szName, Vector &origin, QAngle &angles )
|
|
{
|
|
return GetAttachmentLocal( LookupAttachment( szName ), origin, angles );
|
|
}
|
|
|
|
bool CBaseAnimating::GetAttachmentLocal( int iAttachment, Vector &origin, QAngle &angles )
|
|
{
|
|
matrix3x4_t attachmentToEntity;
|
|
|
|
bool bRet = GetAttachmentLocal( iAttachment, attachmentToEntity );
|
|
MatrixAngles( attachmentToEntity, angles, origin );
|
|
return bRet;
|
|
}
|
|
|
|
bool CBaseAnimating::GetAttachmentLocal( int iAttachment, matrix3x4_t &attachmentToLocal )
|
|
{
|
|
matrix3x4_t attachmentToWorld;
|
|
bool bRet = GetAttachment(iAttachment, attachmentToWorld);
|
|
matrix3x4_t worldToEntity;
|
|
MatrixInvert( EntityToWorldTransform(), worldToEntity );
|
|
ConcatTransforms( worldToEntity, attachmentToWorld, attachmentToLocal );
|
|
return bRet;
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CBaseAnimating::GetEyeballs( Vector &origin, QAngle &angles )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr( );
|
|
if (!pStudioHdr)
|
|
{
|
|
Assert(!"CBaseAnimating::GetAttachment: model missing");
|
|
return;
|
|
}
|
|
|
|
for (int iBodypart = 0; iBodypart < pStudioHdr->numbodyparts(); iBodypart++)
|
|
{
|
|
mstudiobodyparts_t *pBodypart = pStudioHdr->pBodypart( iBodypart );
|
|
for (int iModel = 0; iModel < pBodypart->nummodels; iModel++)
|
|
{
|
|
mstudiomodel_t *pModel = pBodypart->pModel( iModel );
|
|
for (int iEyeball = 0; iEyeball < pModel->numeyeballs; iEyeball++)
|
|
{
|
|
mstudioeyeball_t *pEyeball = pModel->pEyeball( iEyeball );
|
|
matrix3x4_t bonetoworld;
|
|
GetBoneTransform( pEyeball->bone, bonetoworld );
|
|
VectorTransform( pEyeball->org, bonetoworld, origin );
|
|
MatrixAngles( bonetoworld, angles ); // ???
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
int CBaseAnimating::FindTransitionSequence( int iCurrentSequence, int iGoalSequence, int *piDir )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
|
|
if (piDir == NULL)
|
|
{
|
|
int iDir = 1;
|
|
int sequence = ::FindTransitionSequence( GetModelPtr(), iCurrentSequence, iGoalSequence, &iDir );
|
|
if (iDir != 1)
|
|
return -1;
|
|
else
|
|
return sequence;
|
|
}
|
|
|
|
return ::FindTransitionSequence( GetModelPtr(), iCurrentSequence, iGoalSequence, piDir );
|
|
}
|
|
|
|
|
|
bool CBaseAnimating::GotoSequence( int iCurrentSequence, float flCurrentCycle, float flCurrentRate, int iGoalSequence, int &nNextSequence, float &flNextCycle, int &iNextDir )
|
|
{
|
|
return ::GotoSequence( GetModelPtr(), iCurrentSequence, flCurrentCycle, flCurrentRate, iGoalSequence, nNextSequence, flNextCycle, iNextDir );
|
|
}
|
|
|
|
|
|
int CBaseAnimating::GetEntryNode( int iSequence )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr();
|
|
if (! pstudiohdr)
|
|
return 0;
|
|
|
|
return pstudiohdr->EntryNode( iSequence );
|
|
}
|
|
|
|
|
|
int CBaseAnimating::GetExitNode( int iSequence )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr();
|
|
if (! pstudiohdr)
|
|
return 0;
|
|
|
|
return pstudiohdr->ExitNode( iSequence );
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
|
|
void CBaseAnimating::SetBodygroup( int iGroup, int iValue )
|
|
{
|
|
// SetBodygroup is not supported on pending dynamic models. Wait for it to load!
|
|
// XXX TODO we could buffer up the group and value if we really needed to. -henryg
|
|
Assert( GetModelPtr() );
|
|
int newBody = m_nBody;
|
|
::SetBodygroup( GetModelPtr( ), newBody, iGroup, iValue );
|
|
m_nBody = newBody;
|
|
}
|
|
|
|
int CBaseAnimating::GetBodygroup( int iGroup )
|
|
{
|
|
Assert( IsDynamicModelLoading() || GetModelPtr() );
|
|
return IsDynamicModelLoading() ? 0 : ::GetBodygroup( GetModelPtr( ), m_nBody, iGroup );
|
|
}
|
|
|
|
const char *CBaseAnimating::GetBodygroupName( int iGroup )
|
|
{
|
|
Assert( IsDynamicModelLoading() || GetModelPtr() );
|
|
return IsDynamicModelLoading() ? "" : ::GetBodygroupName( GetModelPtr( ), iGroup );
|
|
}
|
|
|
|
int CBaseAnimating::FindBodygroupByName( const char *name )
|
|
{
|
|
Assert( IsDynamicModelLoading() || GetModelPtr() );
|
|
return IsDynamicModelLoading() ? -1 : ::FindBodygroupByName( GetModelPtr( ), name );
|
|
}
|
|
|
|
int CBaseAnimating::GetBodygroupCount( int iGroup )
|
|
{
|
|
Assert( IsDynamicModelLoading() || GetModelPtr() );
|
|
return IsDynamicModelLoading() ? 0 : ::GetBodygroupCount( GetModelPtr( ), iGroup );
|
|
}
|
|
|
|
int CBaseAnimating::GetNumBodyGroups( void )
|
|
{
|
|
Assert( IsDynamicModelLoading() || GetModelPtr() );
|
|
return IsDynamicModelLoading() ? 0 : ::GetNumBodyGroups( GetModelPtr( ) );
|
|
}
|
|
|
|
int CBaseAnimating::ExtractBbox( int sequence, Vector& mins, Vector& maxs )
|
|
{
|
|
Assert( IsDynamicModelLoading() || GetModelPtr() );
|
|
return IsDynamicModelLoading() ? 0 : ::ExtractBbox( GetModelPtr( ), sequence, mins, maxs );
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
|
|
void CBaseAnimating::SetSequenceBox( void )
|
|
{
|
|
Vector mins, maxs;
|
|
|
|
// Get sequence bbox
|
|
if ( ExtractBbox( GetSequence(), mins, maxs ) )
|
|
{
|
|
// expand box for rotation
|
|
// find min / max for rotations
|
|
float yaw = GetLocalAngles().y * (M_PI / 180.0);
|
|
|
|
Vector xvector, yvector;
|
|
xvector.x = cos(yaw);
|
|
xvector.y = sin(yaw);
|
|
yvector.x = -sin(yaw);
|
|
yvector.y = cos(yaw);
|
|
Vector bounds[2];
|
|
|
|
bounds[0] = mins;
|
|
bounds[1] = maxs;
|
|
|
|
Vector rmin( 9999, 9999, 9999 );
|
|
Vector rmax( -9999, -9999, -9999 );
|
|
Vector base, transformed;
|
|
|
|
for (int i = 0; i <= 1; i++ )
|
|
{
|
|
base.x = bounds[i].x;
|
|
for ( int j = 0; j <= 1; j++ )
|
|
{
|
|
base.y = bounds[j].y;
|
|
for ( int k = 0; k <= 1; k++ )
|
|
{
|
|
base.z = bounds[k].z;
|
|
|
|
// transform the point
|
|
transformed.x = xvector.x*base.x + yvector.x*base.y;
|
|
transformed.y = xvector.y*base.x + yvector.y*base.y;
|
|
transformed.z = base.z;
|
|
|
|
for ( int l = 0; l < 3; l++ )
|
|
{
|
|
if (transformed[l] < rmin[l])
|
|
rmin[l] = transformed[l];
|
|
if (transformed[l] > rmax[l])
|
|
rmax[l] = transformed[l];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
rmin.z = 0;
|
|
rmax.z = rmin.z + 1;
|
|
UTIL_SetSize( this, rmin, rmax );
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
int CBaseAnimating::RegisterPrivateActivity( const char *pszActivityName )
|
|
{
|
|
return ActivityList_RegisterPrivateActivity( pszActivityName );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Notifies the console that this entity could not retrieve an
|
|
// animation sequence for the specified activity. This probably means
|
|
// there's a typo in the model QC file, or the sequence is missing
|
|
// entirely.
|
|
//
|
|
//
|
|
// Input : iActivity - The activity that failed to resolve to a sequence.
|
|
//
|
|
//
|
|
// NOTE : IMPORTANT - Something needs to be done so that private activities
|
|
// (which are allowed to collide in the activity list) remember each
|
|
// entity that registered an activity there, and the activity name
|
|
// each character registered.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::ReportMissingActivity( int iActivity )
|
|
{
|
|
Msg( "%s has no sequence for act:%s\n", GetClassname(), ActivityList_NameForIndex(iActivity) );
|
|
}
|
|
|
|
|
|
LocalFlexController_t CBaseAnimating::GetNumFlexControllers( void )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr( );
|
|
if (! pstudiohdr)
|
|
return LocalFlexController_t(0);
|
|
|
|
return pstudiohdr->numflexcontrollers();
|
|
}
|
|
|
|
|
|
const char *CBaseAnimating::GetFlexDescFacs( int iFlexDesc )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr( );
|
|
if (! pstudiohdr)
|
|
return 0;
|
|
|
|
mstudioflexdesc_t *pflexdesc = pstudiohdr->pFlexdesc( iFlexDesc );
|
|
|
|
return pflexdesc->pszFACS( );
|
|
}
|
|
|
|
const char *CBaseAnimating::GetFlexControllerName( LocalFlexController_t iFlexController )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr( );
|
|
if (! pstudiohdr)
|
|
return 0;
|
|
|
|
mstudioflexcontroller_t *pflexcontroller = pstudiohdr->pFlexcontroller( iFlexController );
|
|
|
|
return pflexcontroller->pszName( );
|
|
}
|
|
|
|
const char *CBaseAnimating::GetFlexControllerType( LocalFlexController_t iFlexController )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr( );
|
|
if (! pstudiohdr)
|
|
return 0;
|
|
|
|
mstudioflexcontroller_t *pflexcontroller = pstudiohdr->pFlexcontroller( iFlexController );
|
|
|
|
return pflexcontroller->pszType( );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Converts the ground speed of the animating entity into a true velocity
|
|
// Output : Vector - velocity of the character at its current m_flGroundSpeed
|
|
//-----------------------------------------------------------------------------
|
|
Vector CBaseAnimating::GetGroundSpeedVelocity( void )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr();
|
|
if (!pstudiohdr)
|
|
return vec3_origin;
|
|
|
|
QAngle vecAngles;
|
|
Vector vecVelocity;
|
|
|
|
vecAngles.y = GetSequenceMoveYaw( GetSequence() );
|
|
vecAngles.x = 0;
|
|
vecAngles.z = 0;
|
|
|
|
vecAngles.y += GetLocalAngles().y;
|
|
|
|
AngleVectors( vecAngles, &vecVelocity );
|
|
|
|
vecVelocity = vecVelocity * m_flGroundSpeed;
|
|
|
|
return vecVelocity;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
float CBaseAnimating::GetInstantaneousVelocity( float flInterval )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr( );
|
|
if (! pstudiohdr)
|
|
return 0;
|
|
|
|
// FIXME: someone needs to check for last frame, etc.
|
|
float flNextCycle = GetCycle() + flInterval * GetSequenceCycleRate( GetSequence() ) * m_flPlaybackRate;
|
|
|
|
Vector vecVelocity;
|
|
Studio_SeqVelocity( pstudiohdr, GetSequence(), flNextCycle, GetPoseParameterArray(), vecVelocity );
|
|
vecVelocity *= m_flPlaybackRate;
|
|
|
|
return vecVelocity.Length();
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
float CBaseAnimating::GetEntryVelocity( int iSequence )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr( );
|
|
if (! pstudiohdr)
|
|
return 0;
|
|
|
|
Vector vecVelocity;
|
|
Studio_SeqVelocity( pstudiohdr, iSequence, 0.0, GetPoseParameterArray(), vecVelocity );
|
|
|
|
return vecVelocity.Length();
|
|
}
|
|
|
|
float CBaseAnimating::GetExitVelocity( int iSequence )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr( );
|
|
if (! pstudiohdr)
|
|
return 0;
|
|
|
|
Vector vecVelocity;
|
|
Studio_SeqVelocity( pstudiohdr, iSequence, 1.0, GetPoseParameterArray(), vecVelocity );
|
|
|
|
return vecVelocity.Length();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseAnimating::GetIntervalMovement( float flIntervalUsed, bool &bMoveSeqFinished, Vector &newPosition, QAngle &newAngles )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr( );
|
|
if (! pstudiohdr || !pstudiohdr->SequencesAvailable())
|
|
return false;
|
|
|
|
float flComputedCycleRate = GetSequenceCycleRate( GetSequence() );
|
|
|
|
float flNextCycle = GetCycle() + flIntervalUsed * flComputedCycleRate * m_flPlaybackRate;
|
|
|
|
if ((!m_bSequenceLoops) && flNextCycle > 1.0)
|
|
{
|
|
flIntervalUsed = GetCycle() / (flComputedCycleRate * m_flPlaybackRate);
|
|
flNextCycle = 1.0;
|
|
bMoveSeqFinished = true;
|
|
}
|
|
else
|
|
{
|
|
bMoveSeqFinished = false;
|
|
}
|
|
|
|
Vector deltaPos;
|
|
QAngle deltaAngles;
|
|
|
|
if (Studio_SeqMovement( pstudiohdr, GetSequence(), GetCycle(), flNextCycle, GetPoseParameterArray(), deltaPos, deltaAngles ))
|
|
{
|
|
VectorYawRotate( deltaPos, GetLocalAngles().y, deltaPos );
|
|
newPosition = GetLocalOrigin() + deltaPos;
|
|
newAngles.Init();
|
|
newAngles.y = GetLocalAngles().y + deltaAngles.y;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
newPosition = GetLocalOrigin();
|
|
newAngles = GetLocalAngles();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseAnimating::GetSequenceMovement( int nSequence, float fromCycle, float toCycle, Vector &deltaPosition, QAngle &deltaAngles )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr( );
|
|
if (! pstudiohdr)
|
|
return false;
|
|
|
|
return Studio_SeqMovement( pstudiohdr, nSequence, fromCycle, toCycle, GetPoseParameterArray(), deltaPosition, deltaAngles );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: find frame where they animation has moved a given distance.
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
float CBaseAnimating::GetMovementFrame( float flDist )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr( );
|
|
if (! pstudiohdr)
|
|
return 0;
|
|
|
|
float t = Studio_FindSeqDistance( pstudiohdr, GetSequence(), GetPoseParameterArray(), flDist );
|
|
|
|
return t;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: does a specific sequence have movement?
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseAnimating::HasMovement( int iSequence )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr( );
|
|
if (! pstudiohdr)
|
|
return false;
|
|
|
|
// FIXME: this needs to check to see if there are keys, and the object is walking
|
|
Vector deltaPos;
|
|
QAngle deltaAngles;
|
|
if (Studio_SeqMovement( pstudiohdr, iSequence, 0.0f, 1.0f, GetPoseParameterArray(), deltaPos, deltaAngles ))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *szModelName -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::SetModel( const char *szModelName )
|
|
{
|
|
MDLCACHE_CRITICAL_SECTION();
|
|
|
|
// delete exiting studio model container
|
|
UnlockStudioHdr();
|
|
delete m_pStudioHdr;
|
|
m_pStudioHdr = NULL;
|
|
|
|
if ( szModelName[0] )
|
|
{
|
|
int modelIndex = modelinfo->GetModelIndex( szModelName );
|
|
const model_t *model = modelinfo->GetModel( modelIndex );
|
|
if ( model && ( modelinfo->GetModelType( model ) != mod_studio ) )
|
|
{
|
|
Msg( "Setting CBaseAnimating to non-studio model %s (type:%i)\n", szModelName, modelinfo->GetModelType( model ) );
|
|
}
|
|
}
|
|
|
|
if ( m_boneCacheHandle )
|
|
{
|
|
Studio_DestroyBoneCache( m_boneCacheHandle );
|
|
m_boneCacheHandle = 0;
|
|
}
|
|
|
|
UTIL_SetModel( this, szModelName );
|
|
|
|
InitBoneControllers( );
|
|
SetSequence( 0 );
|
|
|
|
PopulatePoseParameters();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input :
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::LockStudioHdr()
|
|
{
|
|
AUTO_LOCK( m_StudioHdrInitLock );
|
|
const model_t *mdl = GetModel();
|
|
if (mdl)
|
|
{
|
|
MDLHandle_t hStudioHdr = modelinfo->GetCacheHandle( mdl );
|
|
if ( hStudioHdr != MDLHANDLE_INVALID )
|
|
{
|
|
const studiohdr_t *pStudioHdr = mdlcache->LockStudioHdr( hStudioHdr );
|
|
CStudioHdr *pStudioHdrContainer = NULL;
|
|
if ( !m_pStudioHdr )
|
|
{
|
|
if ( pStudioHdr )
|
|
{
|
|
pStudioHdrContainer = new CStudioHdr;
|
|
pStudioHdrContainer->Init( pStudioHdr, mdlcache );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pStudioHdrContainer = m_pStudioHdr;
|
|
}
|
|
|
|
Assert( ( pStudioHdr == NULL && pStudioHdrContainer == NULL ) || pStudioHdrContainer->GetRenderHdr() == pStudioHdr );
|
|
|
|
if ( pStudioHdrContainer && pStudioHdrContainer->GetVirtualModel() )
|
|
{
|
|
MDLHandle_t hVirtualModel = VoidPtrToMDLHandle( pStudioHdrContainer->GetRenderHdr()->VirtualModel() );
|
|
mdlcache->LockStudioHdr( hVirtualModel );
|
|
}
|
|
m_pStudioHdr = pStudioHdrContainer; // must be last to ensure virtual model correctly set up
|
|
}
|
|
}
|
|
}
|
|
|
|
void CBaseAnimating::UnlockStudioHdr()
|
|
{
|
|
if ( m_pStudioHdr )
|
|
{
|
|
const model_t *mdl = GetModel();
|
|
if (mdl)
|
|
{
|
|
mdlcache->UnlockStudioHdr( modelinfo->GetCacheHandle( mdl ) );
|
|
if ( m_pStudioHdr->GetVirtualModel() )
|
|
{
|
|
MDLHandle_t hVirtualModel = VoidPtrToMDLHandle( m_pStudioHdr->GetRenderHdr()->VirtualModel() );
|
|
mdlcache->UnlockStudioHdr( hVirtualModel );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: return the index to the shared bone cache
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
CBoneCache *CBaseAnimating::GetBoneCache( void )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr( );
|
|
Assert(pStudioHdr);
|
|
|
|
CBoneCache *pcache = Studio_GetBoneCache( m_boneCacheHandle );
|
|
int boneMask = BONE_USED_BY_HITBOX | BONE_USED_BY_ATTACHMENT;
|
|
|
|
// TF queries these bones to position weapons when players are killed
|
|
#if defined( TF_DLL )
|
|
boneMask |= BONE_USED_BY_BONE_MERGE;
|
|
#endif
|
|
if ( pcache )
|
|
{
|
|
if ( pcache->IsValid( gpGlobals->curtime ) && (pcache->m_boneMask & boneMask) == boneMask && pcache->m_timeValid <= gpGlobals->curtime)
|
|
{
|
|
// Msg("%s:%s:%s (%x:%x:%8.4f) cache\n", GetClassname(), GetDebugName(), STRING(GetModelName()), boneMask, pcache->m_boneMask, pcache->m_timeValid );
|
|
// in memory and still valid, use it!
|
|
return pcache;
|
|
}
|
|
// in memory, but missing some of the bone masks
|
|
if ( (pcache->m_boneMask & boneMask) != boneMask )
|
|
{
|
|
Studio_DestroyBoneCache( m_boneCacheHandle );
|
|
m_boneCacheHandle = 0;
|
|
pcache = NULL;
|
|
}
|
|
}
|
|
|
|
matrix3x4_t bonetoworld[MAXSTUDIOBONES];
|
|
SetupBones( bonetoworld, boneMask );
|
|
|
|
if ( pcache )
|
|
{
|
|
// still in memory but out of date, refresh the bones.
|
|
pcache->UpdateBones( bonetoworld, pStudioHdr->numbones(), gpGlobals->curtime );
|
|
}
|
|
else
|
|
{
|
|
bonecacheparams_t params;
|
|
params.pStudioHdr = pStudioHdr;
|
|
params.pBoneToWorld = bonetoworld;
|
|
params.curtime = gpGlobals->curtime;
|
|
params.boneMask = boneMask;
|
|
|
|
m_boneCacheHandle = Studio_CreateBoneCache( params );
|
|
pcache = Studio_GetBoneCache( m_boneCacheHandle );
|
|
}
|
|
Assert(pcache);
|
|
return pcache;
|
|
}
|
|
|
|
|
|
void CBaseAnimating::InvalidateBoneCache( void )
|
|
{
|
|
Studio_InvalidateBoneCache( m_boneCacheHandle );
|
|
}
|
|
|
|
bool CBaseAnimating::TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
|
|
{
|
|
// Return a special case for scaled physics objects
|
|
if ( GetModelScale() != 1.0f )
|
|
{
|
|
IPhysicsObject *pPhysObject = VPhysicsGetObject();
|
|
Vector vecPosition;
|
|
QAngle vecAngles;
|
|
pPhysObject->GetPosition( &vecPosition, &vecAngles );
|
|
const CPhysCollide *pScaledCollide = pPhysObject->GetCollide();
|
|
physcollision->TraceBox( ray, pScaledCollide, vecPosition, vecAngles, &tr );
|
|
|
|
return tr.DidHit();
|
|
}
|
|
|
|
if ( IsSolidFlagSet( FSOLID_CUSTOMRAYTEST ))
|
|
{
|
|
if (!TestHitboxes( ray, fContentsMask, tr ))
|
|
return true;
|
|
|
|
return tr.DidHit();
|
|
}
|
|
|
|
// We shouldn't get here.
|
|
Assert(0);
|
|
return false;
|
|
}
|
|
|
|
bool CBaseAnimating::TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr( );
|
|
if (!pStudioHdr)
|
|
{
|
|
Assert(!"CBaseAnimating::GetBonePosition: model missing");
|
|
return false;
|
|
}
|
|
|
|
mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( m_nHitboxSet );
|
|
if ( !set || !set->numhitboxes )
|
|
return false;
|
|
|
|
CBoneCache *pcache = GetBoneCache( );
|
|
|
|
matrix3x4_t *hitboxbones[MAXSTUDIOBONES];
|
|
pcache->ReadCachedBonePointers( hitboxbones, pStudioHdr->numbones() );
|
|
|
|
if ( TraceToStudio( physprops, ray, pStudioHdr, set, hitboxbones, fContentsMask, GetAbsOrigin(), GetModelScale(), tr ) )
|
|
{
|
|
mstudiobbox_t *pbox = set->pHitbox( tr.hitbox );
|
|
mstudiobone_t *pBone = pStudioHdr->pBone(pbox->bone);
|
|
tr.surface.name = "**studio**";
|
|
tr.surface.flags = SURF_HITBOX;
|
|
tr.surface.surfaceProps = physprops->GetSurfaceIndex( pBone->pszSurfaceProp() );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CBaseAnimating::InitBoneControllers ( void ) // FIXME: rename
|
|
{
|
|
int i;
|
|
|
|
CStudioHdr *pStudioHdr = GetModelPtr( );
|
|
if (!pStudioHdr)
|
|
return;
|
|
|
|
int nBoneControllerCount = pStudioHdr->numbonecontrollers();
|
|
if ( nBoneControllerCount > NUM_BONECTRLS )
|
|
{
|
|
nBoneControllerCount = NUM_BONECTRLS;
|
|
|
|
#ifdef _DEBUG
|
|
Warning( "Model %s has too many bone controllers! (Max %d allowed)\n", pStudioHdr->pszName(), NUM_BONECTRLS );
|
|
#endif
|
|
}
|
|
|
|
for (i = 0; i < nBoneControllerCount; i++)
|
|
{
|
|
SetBoneController( i, 0.0 );
|
|
}
|
|
|
|
Assert( pStudioHdr->SequencesAvailable() );
|
|
|
|
if ( pStudioHdr->SequencesAvailable() )
|
|
{
|
|
for (i = 0; i < pStudioHdr->GetNumPoseParameters(); i++)
|
|
{
|
|
SetPoseParameter( i, 0.0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
float CBaseAnimating::SetBoneController ( int iController, float flValue )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
|
|
CStudioHdr *pmodel = (CStudioHdr*)GetModelPtr();
|
|
|
|
Assert(iController >= 0 && iController < NUM_BONECTRLS);
|
|
|
|
float newValue;
|
|
float retVal = Studio_SetController( pmodel, iController, flValue, newValue );
|
|
|
|
float &val = m_flEncodedController.GetForModify( iController );
|
|
val = newValue;
|
|
return retVal;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
float CBaseAnimating::GetBoneController ( int iController )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
|
|
CStudioHdr *pmodel = (CStudioHdr*)GetModelPtr();
|
|
|
|
return Studio_GetController( pmodel, iController, m_flEncodedController[iController] );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : Returns velcocity of the NPC from it's animation.
|
|
// If physically simulated gets velocity from physics object
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CBaseAnimating::GetVelocity(Vector *vVelocity, AngularImpulse *vAngVelocity)
|
|
{
|
|
if ( GetMoveType() == MOVETYPE_VPHYSICS )
|
|
{
|
|
BaseClass::GetVelocity(vVelocity,vAngVelocity);
|
|
}
|
|
else if ( !(GetFlags() & FL_ONGROUND) )
|
|
{
|
|
BaseClass::GetVelocity(vVelocity,vAngVelocity);
|
|
}
|
|
else
|
|
{
|
|
if (vVelocity != NULL)
|
|
{
|
|
Vector vRawVel;
|
|
|
|
GetSequenceLinearMotion( GetSequence(), &vRawVel );
|
|
|
|
// Build a rotation matrix from NPC orientation
|
|
matrix3x4_t fRotateMatrix;
|
|
AngleMatrix(GetLocalAngles(), fRotateMatrix);
|
|
VectorRotate( vRawVel, fRotateMatrix, *vVelocity);
|
|
}
|
|
if (vAngVelocity != NULL)
|
|
{
|
|
QAngle tmp = GetLocalAngularVelocity();
|
|
QAngleToAngularImpulse( tmp, *vAngVelocity );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
|
|
void CBaseAnimating::GetSkeleton( CStudioHdr *pStudioHdr, Vector pos[], Quaternion q[], int boneMask )
|
|
{
|
|
if(!pStudioHdr)
|
|
{
|
|
Assert(!"CBaseAnimating::GetSkeleton() without a model");
|
|
return;
|
|
}
|
|
|
|
IBoneSetup boneSetup( pStudioHdr, boneMask, GetPoseParameterArray() );
|
|
boneSetup.InitPose( pos, q );
|
|
|
|
boneSetup.AccumulatePose( pos, q, GetSequence(), GetCycle(), 1.0, gpGlobals->curtime, m_pIk );
|
|
|
|
if ( m_pIk )
|
|
{
|
|
CIKContext auto_ik;
|
|
auto_ik.Init( pStudioHdr, GetAbsAngles(), GetAbsOrigin(), gpGlobals->curtime, 0, boneMask );
|
|
boneSetup.CalcAutoplaySequences( pos, q, gpGlobals->curtime, &auto_ik );
|
|
}
|
|
else
|
|
{
|
|
boneSetup.CalcAutoplaySequences( pos, q, gpGlobals->curtime, NULL );
|
|
}
|
|
boneSetup.CalcBoneAdj( pos, q, GetEncodedControllerArray() );
|
|
}
|
|
|
|
int CBaseAnimating::DrawDebugTextOverlays(void)
|
|
{
|
|
int text_offset = BaseClass::DrawDebugTextOverlays();
|
|
|
|
if (m_debugOverlays & OVERLAY_TEXT_BIT)
|
|
{
|
|
// ----------------
|
|
// Print Look time
|
|
// ----------------
|
|
char tempstr[1024];
|
|
Q_snprintf(tempstr, sizeof(tempstr), "Sequence: (%3d) %s",GetSequence(), GetSequenceName( GetSequence() ) );
|
|
EntityText(text_offset,tempstr,0);
|
|
text_offset++;
|
|
const char *pActname = GetSequenceActivityName(GetSequence());
|
|
if ( pActname && strlen(pActname) )
|
|
{
|
|
Q_snprintf(tempstr, sizeof(tempstr), "Activity %s", pActname );
|
|
EntityText(text_offset,tempstr,0);
|
|
text_offset++;
|
|
}
|
|
|
|
Q_snprintf(tempstr, sizeof(tempstr), "Cycle: %.5f (%.5f)", (float)GetCycle(), m_flAnimTime.Get() );
|
|
EntityText(text_offset,tempstr,0);
|
|
text_offset++;
|
|
}
|
|
|
|
// Visualize attachment points
|
|
if ( m_debugOverlays & OVERLAY_ATTACHMENTS_BIT )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
|
|
if ( pStudioHdr )
|
|
{
|
|
Vector vecPos, vecForward, vecRight, vecUp;
|
|
char tempstr[256];
|
|
|
|
// Iterate all the stored attachments
|
|
for ( int i = 1; i <= pStudioHdr->GetNumAttachments(); i++ )
|
|
{
|
|
GetAttachment( i, vecPos, &vecForward, &vecRight, &vecUp );
|
|
|
|
// Red - forward, green - right, blue - up
|
|
NDebugOverlay::Line( vecPos, vecPos + ( vecForward * 4.0f ), 255, 0, 0, true, 0.05f );
|
|
NDebugOverlay::Line( vecPos, vecPos + ( vecRight * 4.0f ), 0, 255, 0, true, 0.05f );
|
|
NDebugOverlay::Line( vecPos, vecPos + ( vecUp * 4.0f ), 0, 0, 255, true, 0.05f );
|
|
|
|
Q_snprintf( tempstr, sizeof(tempstr), " < %s (%d)", pStudioHdr->pAttachment(i-1).pszName(), i );
|
|
NDebugOverlay::Text( vecPos, tempstr, true, 0.05f );
|
|
}
|
|
}
|
|
}
|
|
|
|
return text_offset;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Force a clientside-animating entity to reset it's frame
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::ResetClientsideFrame( void )
|
|
{
|
|
// TODO: Once we can chain MSG_ENTITY messages, use one of them
|
|
m_bClientSideFrameReset = !(bool)m_bClientSideFrameReset;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the origin at which to play an inputted dispatcheffect
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::GetInputDispatchEffectPosition( const char *sInputString, Vector &pOrigin, QAngle &pAngles )
|
|
{
|
|
// See if there's a specified attachment point
|
|
int iAttachment;
|
|
if ( GetModelPtr() && sscanf( sInputString, "%d", &iAttachment ) )
|
|
{
|
|
if ( !GetAttachment( iAttachment, pOrigin, pAngles ) )
|
|
{
|
|
Msg( "ERROR: Mapmaker tried to spawn DispatchEffect %s, but %s has no attachment %d\n",
|
|
sInputString, STRING(GetModelName()), iAttachment );
|
|
}
|
|
return;
|
|
}
|
|
|
|
BaseClass::GetInputDispatchEffectPosition( sInputString, pOrigin, pAngles );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : setnum -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::SetHitboxSet( int setnum )
|
|
{
|
|
#ifdef _DEBUG
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
if ( !pStudioHdr )
|
|
return;
|
|
|
|
if (setnum > pStudioHdr->numhitboxsets())
|
|
{
|
|
// Warn if an bogus hitbox set is being used....
|
|
static bool s_bWarned = false;
|
|
if (!s_bWarned)
|
|
{
|
|
Warning("Using bogus hitbox set in entity %s!\n", GetClassname() );
|
|
s_bWarned = true;
|
|
}
|
|
setnum = 0;
|
|
}
|
|
#endif
|
|
|
|
m_nHitboxSet = setnum;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *setname -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::SetHitboxSetByName( const char *setname )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
m_nHitboxSet = FindHitboxSetByName( GetModelPtr(), setname );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseAnimating::GetHitboxSet( void )
|
|
{
|
|
return m_nHitboxSet;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : char const
|
|
//-----------------------------------------------------------------------------
|
|
const char *CBaseAnimating::GetHitboxSetName( void )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
return ::GetHitboxSetName( GetModelPtr(), m_nHitboxSet );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseAnimating::GetHitboxSetCount( void )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
return ::GetHitboxSetCount( GetModelPtr() );
|
|
}
|
|
|
|
static Vector hullcolor[8] =
|
|
{
|
|
Vector( 1.0, 1.0, 1.0 ),
|
|
Vector( 1.0, 0.5, 0.5 ),
|
|
Vector( 0.5, 1.0, 0.5 ),
|
|
Vector( 1.0, 1.0, 0.5 ),
|
|
Vector( 0.5, 0.5, 1.0 ),
|
|
Vector( 1.0, 0.5, 1.0 ),
|
|
Vector( 0.5, 1.0, 1.0 ),
|
|
Vector( 1.0, 1.0, 1.0 )
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Send the current hitboxes for this model to the client ( to compare with
|
|
// r_drawentities 3 client side boxes ).
|
|
// WARNING: This uses a ton of bandwidth, only use on a listen server
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::DrawServerHitboxes( float duration /*= 0.0f*/, bool monocolor /*= false*/ )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
if ( !pStudioHdr )
|
|
return;
|
|
|
|
mstudiohitboxset_t *set =pStudioHdr->pHitboxSet( m_nHitboxSet );
|
|
if ( !set )
|
|
return;
|
|
|
|
Vector position;
|
|
QAngle angles;
|
|
|
|
int r = 0;
|
|
int g = 0;
|
|
int b = 255;
|
|
|
|
for ( int i = 0; i < set->numhitboxes; i++ )
|
|
{
|
|
mstudiobbox_t *pbox = set->pHitbox( i );
|
|
|
|
GetBonePosition( pbox->bone, position, angles );
|
|
|
|
if ( !monocolor )
|
|
{
|
|
int j = (pbox->group % 8);
|
|
|
|
r = ( int ) ( 255.0f * hullcolor[j][0] );
|
|
g = ( int ) ( 255.0f * hullcolor[j][1] );
|
|
b = ( int ) ( 255.0f * hullcolor[j][2] );
|
|
}
|
|
|
|
NDebugOverlay::BoxAngles( position, pbox->bbmin * GetModelScale(), pbox->bbmax * GetModelScale(), angles, r, g, b, 0 ,duration );
|
|
}
|
|
}
|
|
|
|
|
|
void CBaseAnimating::DrawRawSkeleton( matrix3x4_t boneToWorld[], int boneMask, bool noDepthTest, float duration, bool monocolor )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
if ( !pStudioHdr )
|
|
return;
|
|
|
|
int i;
|
|
int r = 255;
|
|
int g = 255;
|
|
int b = monocolor ? 255 : 0;
|
|
|
|
|
|
for (i = 0; i < pStudioHdr->numbones(); i++)
|
|
{
|
|
if (pStudioHdr->pBone( i )->flags & boneMask)
|
|
{
|
|
Vector p1;
|
|
MatrixPosition( boneToWorld[i], p1 );
|
|
if ( pStudioHdr->pBone( i )->parent != -1 )
|
|
{
|
|
Vector p2;
|
|
MatrixPosition( boneToWorld[pStudioHdr->pBone( i )->parent], p2 );
|
|
NDebugOverlay::Line( p1, p2, r, g, b, noDepthTest, duration );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int CBaseAnimating::GetHitboxBone( int hitboxIndex )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
if ( pStudioHdr )
|
|
{
|
|
mstudiohitboxset_t *set =pStudioHdr->pHitboxSet( m_nHitboxSet );
|
|
if ( set && hitboxIndex < set->numhitboxes )
|
|
{
|
|
return set->pHitbox( hitboxIndex )->bone;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Computes a box that surrounds all hitboxes
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseAnimating::ComputeHitboxSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs )
|
|
{
|
|
// Note that this currently should not be called during Relink because of IK.
|
|
// The code below recomputes bones so as to get at the hitboxes,
|
|
// which causes IK to trigger, which causes raycasts against the other entities to occur,
|
|
// which is illegal to do while in the Relink phase.
|
|
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
if (!pStudioHdr)
|
|
return false;
|
|
|
|
mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( m_nHitboxSet );
|
|
if ( !set || !set->numhitboxes )
|
|
return false;
|
|
|
|
CBoneCache *pCache = GetBoneCache();
|
|
|
|
// Compute a box in world space that surrounds this entity
|
|
pVecWorldMins->Init( FLT_MAX, FLT_MAX, FLT_MAX );
|
|
pVecWorldMaxs->Init( -FLT_MAX, -FLT_MAX, -FLT_MAX );
|
|
|
|
Vector vecBoxAbsMins, vecBoxAbsMaxs;
|
|
for ( int i = 0; i < set->numhitboxes; i++ )
|
|
{
|
|
mstudiobbox_t *pbox = set->pHitbox(i);
|
|
matrix3x4_t *pMatrix = pCache->GetCachedBone(pbox->bone);
|
|
|
|
if ( pMatrix )
|
|
{
|
|
TransformAABB( *pMatrix, pbox->bbmin * GetModelScale(), pbox->bbmax * GetModelScale(), vecBoxAbsMins, vecBoxAbsMaxs );
|
|
VectorMin( *pVecWorldMins, vecBoxAbsMins, *pVecWorldMins );
|
|
VectorMax( *pVecWorldMaxs, vecBoxAbsMaxs, *pVecWorldMaxs );
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Computes a box that surrounds all hitboxes, in entity space
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseAnimating::ComputeEntitySpaceHitboxSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs )
|
|
{
|
|
// Note that this currently should not be called during position recomputation because of IK.
|
|
// The code below recomputes bones so as to get at the hitboxes,
|
|
// which causes IK to trigger, which causes raycasts against the other entities to occur,
|
|
// which is illegal to do while in the computeabsposition phase.
|
|
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
if (!pStudioHdr)
|
|
return false;
|
|
|
|
mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( m_nHitboxSet );
|
|
if ( !set || !set->numhitboxes )
|
|
return false;
|
|
|
|
CBoneCache *pCache = GetBoneCache();
|
|
matrix3x4_t *hitboxbones[MAXSTUDIOBONES];
|
|
pCache->ReadCachedBonePointers( hitboxbones, pStudioHdr->numbones() );
|
|
|
|
// Compute a box in world space that surrounds this entity
|
|
pVecWorldMins->Init( FLT_MAX, FLT_MAX, FLT_MAX );
|
|
pVecWorldMaxs->Init( -FLT_MAX, -FLT_MAX, -FLT_MAX );
|
|
|
|
matrix3x4_t worldToEntity, boneToEntity;
|
|
MatrixInvert( EntityToWorldTransform(), worldToEntity );
|
|
|
|
Vector vecBoxAbsMins, vecBoxAbsMaxs;
|
|
for ( int i = 0; i < set->numhitboxes; i++ )
|
|
{
|
|
mstudiobbox_t *pbox = set->pHitbox(i);
|
|
|
|
ConcatTransforms( worldToEntity, *hitboxbones[pbox->bone], boneToEntity );
|
|
TransformAABB( boneToEntity, pbox->bbmin, pbox->bbmax, vecBoxAbsMins, vecBoxAbsMaxs );
|
|
VectorMin( *pVecWorldMins, vecBoxAbsMins, *pVecWorldMins );
|
|
VectorMax( *pVecWorldMaxs, vecBoxAbsMaxs, *pVecWorldMaxs );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
int CBaseAnimating::GetPhysicsBone( int boneIndex )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
if ( pStudioHdr )
|
|
{
|
|
if ( boneIndex >= 0 && boneIndex < pStudioHdr->numbones() )
|
|
return pStudioHdr->pBone( boneIndex )->physicsbone;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool CBaseAnimating::LookupHitbox( const char *szName, int& outSet, int& outBox )
|
|
{
|
|
CStudioHdr* pHdr = GetModelPtr();
|
|
|
|
outSet = -1;
|
|
outBox = -1;
|
|
|
|
if( !pHdr )
|
|
return false;
|
|
|
|
for( int set=0; set < pHdr->numhitboxsets(); set++ )
|
|
{
|
|
for( int i = 0; i < pHdr->iHitboxCount(set); i++ )
|
|
{
|
|
mstudiobbox_t* pBox = pHdr->pHitbox( i, set );
|
|
|
|
if( !pBox )
|
|
continue;
|
|
|
|
const char* szBoxName = pBox->pszHitboxName();
|
|
if( Q_stricmp( szBoxName, szName ) == 0 )
|
|
{
|
|
outSet = set;
|
|
outBox = i;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CBaseAnimating::CopyAnimationDataFrom( CBaseAnimating *pSource )
|
|
{
|
|
this->SetModelName( pSource->GetModelName() );
|
|
this->SetModelIndex( pSource->GetModelIndex() );
|
|
this->SetCycle( pSource->GetCycle() );
|
|
this->SetEffects( pSource->GetEffects() );
|
|
this->IncrementInterpolationFrame();
|
|
this->SetSequence( pSource->GetSequence() );
|
|
this->m_flAnimTime = pSource->m_flAnimTime;
|
|
this->m_nBody = pSource->m_nBody;
|
|
this->m_nSkin = pSource->m_nSkin;
|
|
this->LockStudioHdr();
|
|
}
|
|
|
|
int CBaseAnimating::GetHitboxesFrontside( int *boxList, int boxMax, const Vector &normal, float dist )
|
|
{
|
|
int count = 0;
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
if ( pStudioHdr )
|
|
{
|
|
mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( m_nHitboxSet );
|
|
if ( set )
|
|
{
|
|
matrix3x4_t matrix;
|
|
for ( int b = 0; b < set->numhitboxes; b++ )
|
|
{
|
|
mstudiobbox_t *pbox = set->pHitbox( b );
|
|
|
|
GetBoneTransform( pbox->bone, matrix );
|
|
Vector center = (pbox->bbmax + pbox->bbmin) * 0.5;
|
|
Vector centerWs;
|
|
VectorTransform( center, matrix, centerWs );
|
|
if ( DotProduct( centerWs, normal ) >= dist )
|
|
{
|
|
if ( count < boxMax )
|
|
{
|
|
boxList[count] = b;
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
void CBaseAnimating::EnableServerIK()
|
|
{
|
|
if (!m_pIk)
|
|
{
|
|
m_pIk = new CIKContext;
|
|
m_iIKCounter = 0;
|
|
}
|
|
}
|
|
|
|
void CBaseAnimating::DisableServerIK()
|
|
{
|
|
delete m_pIk;
|
|
m_pIk = NULL;
|
|
}
|
|
|
|
Activity CBaseAnimating::GetSequenceActivity( int iSequence )
|
|
{
|
|
if( iSequence == -1 )
|
|
{
|
|
return ACT_INVALID;
|
|
}
|
|
|
|
if ( !GetModelPtr() )
|
|
return ACT_INVALID;
|
|
|
|
return (Activity)::GetSequenceActivity( GetModelPtr(), iSequence );
|
|
}
|
|
|
|
void CBaseAnimating::ModifyOrAppendCriteria( AI_CriteriaSet& set )
|
|
{
|
|
BaseClass::ModifyOrAppendCriteria( set );
|
|
|
|
// TODO
|
|
// Append any animation state parameters here
|
|
}
|
|
|
|
|
|
void CBaseAnimating::DoMuzzleFlash()
|
|
{
|
|
m_nMuzzleFlashParity = (m_nMuzzleFlashParity+1) & ((1 << EF_MUZZLEFLASH_BITS) - 1);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : scale -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::SetModelScale( float scale, float change_duration /*= 0.0f*/ )
|
|
{
|
|
if ( change_duration > 0.0f )
|
|
{
|
|
ModelScale *mvs = ( ModelScale * )CreateDataObject( MODELSCALE );
|
|
mvs->m_flModelScaleStart = m_flModelScale;
|
|
mvs->m_flModelScaleGoal = scale;
|
|
mvs->m_flModelScaleStartTime = gpGlobals->curtime;
|
|
mvs->m_flModelScaleFinishTime = mvs->m_flModelScaleStartTime + change_duration;
|
|
}
|
|
else
|
|
{
|
|
m_flModelScale = scale;
|
|
RefreshCollisionBounds();
|
|
|
|
if ( HasDataObjectType( MODELSCALE ) )
|
|
{
|
|
DestroyDataObject( MODELSCALE );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CBaseAnimating::UpdateModelScale()
|
|
{
|
|
ModelScale *mvs = ( ModelScale * )GetDataObject( MODELSCALE );
|
|
if ( !mvs )
|
|
{
|
|
return;
|
|
}
|
|
|
|
float dt = mvs->m_flModelScaleFinishTime - mvs->m_flModelScaleStartTime;
|
|
Assert( dt > 0.0f );
|
|
|
|
float frac = ( gpGlobals->curtime - mvs->m_flModelScaleStartTime ) / dt;
|
|
frac = clamp( frac, 0.0f, 1.0f );
|
|
|
|
if ( gpGlobals->curtime >= mvs->m_flModelScaleFinishTime )
|
|
{
|
|
m_flModelScale = mvs->m_flModelScaleGoal;
|
|
DestroyDataObject( MODELSCALE );
|
|
}
|
|
else
|
|
{
|
|
m_flModelScale = Lerp( frac, mvs->m_flModelScaleStart, mvs->m_flModelScaleGoal );
|
|
}
|
|
|
|
RefreshCollisionBounds();
|
|
}
|
|
|
|
void CBaseAnimating::RefreshCollisionBounds( void )
|
|
{
|
|
CollisionProp()->RefreshScaledCollisionBounds();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
|
|
{
|
|
if( IsOnFire() )
|
|
return;
|
|
|
|
bool bIsNPC = IsNPC();
|
|
|
|
// Right now this prevents stuff we don't want to catch on fire from catching on fire.
|
|
if( bNPCOnly && bIsNPC == false )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( bIsNPC == true && bCalledByLevelDesigner == false )
|
|
{
|
|
CAI_BaseNPC *pNPC = MyNPCPointer();
|
|
|
|
if ( pNPC && pNPC->AllowedToIgnite() == false )
|
|
return;
|
|
}
|
|
|
|
CEntityFlame *pFlame = CEntityFlame::Create( this );
|
|
if (pFlame)
|
|
{
|
|
pFlame->SetLifetime( flFlameLifetime );
|
|
AddFlag( FL_ONFIRE );
|
|
|
|
SetEffectEntity( pFlame );
|
|
|
|
if ( flSize > 0.0f )
|
|
{
|
|
pFlame->SetSize( flSize );
|
|
}
|
|
}
|
|
|
|
m_OnIgnite.FireOutput( this, this );
|
|
}
|
|
|
|
void CBaseAnimating::IgniteLifetime( float flFlameLifetime )
|
|
{
|
|
if( !IsOnFire() )
|
|
Ignite( 30, false, 0.0f, true );
|
|
|
|
CEntityFlame *pFlame = dynamic_cast<CEntityFlame*>( GetEffectEntity() );
|
|
|
|
if ( !pFlame )
|
|
return;
|
|
|
|
pFlame->SetLifetime( flFlameLifetime );
|
|
}
|
|
|
|
void CBaseAnimating::IgniteNumHitboxFires( int iNumHitBoxFires )
|
|
{
|
|
if( !IsOnFire() )
|
|
Ignite( 30, false, 0.0f, true );
|
|
|
|
CEntityFlame *pFlame = dynamic_cast<CEntityFlame*>( GetEffectEntity() );
|
|
|
|
if ( !pFlame )
|
|
return;
|
|
|
|
pFlame->SetNumHitboxFires( iNumHitBoxFires );
|
|
}
|
|
|
|
void CBaseAnimating::IgniteHitboxFireScale( float flHitboxFireScale )
|
|
{
|
|
if( !IsOnFire() )
|
|
Ignite( 30, false, 0.0f, true );
|
|
|
|
CEntityFlame *pFlame = dynamic_cast<CEntityFlame*>( GetEffectEntity() );
|
|
|
|
if ( !pFlame )
|
|
return;
|
|
|
|
pFlame->SetHitboxFireScale( flHitboxFireScale );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Fades out!
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseAnimating::Dissolve( const char *pMaterialName, float flStartTime, bool bNPCOnly, int nDissolveType, Vector vDissolverOrigin, int iMagnitude )
|
|
{
|
|
// Right now this prevents stuff we don't want to catch on fire from catching on fire.
|
|
if( bNPCOnly && !(GetFlags() & FL_NPC) )
|
|
return false;
|
|
|
|
// Can't dissolve twice
|
|
if ( IsDissolving() )
|
|
return false;
|
|
|
|
bool bRagdollCreated = false;
|
|
CEntityDissolve *pDissolve = CEntityDissolve::Create( this, pMaterialName, flStartTime, nDissolveType, &bRagdollCreated );
|
|
if (pDissolve)
|
|
{
|
|
SetEffectEntity( pDissolve );
|
|
|
|
AddFlag( FL_DISSOLVING );
|
|
m_flDissolveStartTime = flStartTime;
|
|
pDissolve->SetDissolverOrigin( vDissolverOrigin );
|
|
pDissolve->SetMagnitude( iMagnitude );
|
|
}
|
|
|
|
// if this is a ragdoll dissolving, fire an event
|
|
if ( ( CLASS_NONE == Classify() ) && ( ClassMatches( "prop_ragdoll" ) ) )
|
|
{
|
|
IGameEvent *event = gameeventmanager->CreateEvent( "ragdoll_dissolved" );
|
|
if ( event )
|
|
{
|
|
event->SetInt( "entindex", entindex() );
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
}
|
|
|
|
return bRagdollCreated;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Make a model look as though it's burning.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::Scorch( int rate, int floor )
|
|
{
|
|
color32 color = GetRenderColor();
|
|
|
|
if( color.r > floor )
|
|
color.r -= rate;
|
|
|
|
if( color.g > floor )
|
|
color.g -= rate;
|
|
|
|
if( color.b > floor )
|
|
color.b -= rate;
|
|
|
|
SetRenderColor( color.r, color.g, color.b );
|
|
}
|
|
|
|
|
|
void CBaseAnimating::ResetSequence(int nSequence)
|
|
{
|
|
if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
|
|
{
|
|
DevMsg("ResetSequence : %s: %s -> %s\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(nSequence));
|
|
}
|
|
|
|
if ( !SequenceLoops() )
|
|
{
|
|
SetCycle( 0 );
|
|
}
|
|
|
|
// Tracker 17868: If the sequence number didn't actually change, but you call resetsequence info, it changes
|
|
// the newsequenceparity bit which causes the client to call m_flCycle.Reset() which causes a very slight
|
|
// discontinuity in looping animations as they reset around to cycle 0.0. This was causing the parentattached
|
|
// helmet on barney to hitch every time barney's idle cycled back around to its start.
|
|
bool changed = nSequence != GetSequence() ? true : false;
|
|
|
|
SetSequence( nSequence );
|
|
if ( changed || !SequenceLoops() )
|
|
{
|
|
ResetSequenceInfo();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::InputIgnite( inputdata_t &inputdata )
|
|
{
|
|
Ignite( 30, false, 0.0f, true );
|
|
}
|
|
|
|
void CBaseAnimating::InputIgniteLifetime( inputdata_t &inputdata )
|
|
{
|
|
IgniteLifetime( inputdata.value.Float() );
|
|
}
|
|
|
|
void CBaseAnimating::InputIgniteNumHitboxFires( inputdata_t &inputdata )
|
|
{
|
|
IgniteNumHitboxFires( inputdata.value.Int() );
|
|
}
|
|
|
|
void CBaseAnimating::InputIgniteHitboxFireScale( inputdata_t &inputdata )
|
|
{
|
|
IgniteHitboxFireScale( inputdata.value.Float() );
|
|
}
|
|
|
|
void CBaseAnimating::InputBecomeRagdoll( inputdata_t &inputdata )
|
|
{
|
|
BecomeRagdollOnClient( vec3_origin );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::SetFadeDistance( float minFadeDist, float maxFadeDist )
|
|
{
|
|
m_fadeMinDist = minFadeDist;
|
|
m_fadeMaxDist = maxFadeDist;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Async prefetches all anim data used by a particular sequence. Returns true if all of the required data is memory resident
|
|
// Input : iSequence -
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseAnimating::PrefetchSequence( int iSequence )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
if ( !pStudioHdr )
|
|
return true;
|
|
|
|
return Studio_PrefetchSequence( pStudioHdr, iSequence );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseAnimating::IsSequenceLooping( CStudioHdr *pStudioHdr, int iSequence )
|
|
{
|
|
return (::GetSequenceFlags( pStudioHdr, iSequence ) & STUDIO_LOOPING) != 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: model-change notification. Fires on dynamic load completion as well
|
|
//-----------------------------------------------------------------------------
|
|
CStudioHdr *CBaseAnimating::OnNewModel()
|
|
{
|
|
(void) BaseClass::OnNewModel();
|
|
|
|
// TODO: if dynamic, validate m_Sequence and apply queued body group settings?
|
|
if ( IsDynamicModelLoading() )
|
|
{
|
|
// Called while dynamic model still loading -> new model, clear deferred state
|
|
m_bResetSequenceInfoOnLoad = false;
|
|
return NULL;
|
|
}
|
|
|
|
CStudioHdr *hdr = GetModelPtr();
|
|
|
|
if ( m_bResetSequenceInfoOnLoad )
|
|
{
|
|
m_bResetSequenceInfoOnLoad = false;
|
|
ResetSequenceInfo();
|
|
}
|
|
|
|
return hdr;
|
|
}
|