4195 lines
116 KiB
C++
4195 lines
116 KiB
C++
//===== Copyright © 1996-2005, 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"
|
|
#if defined( HL2_EP3 ) || defined( INFESTED_DLL )
|
|
#include "EntityFreezing.h"
|
|
#endif
|
|
#include "ai_basenpc.h"
|
|
#include "physics_prop_ragdoll.h"
|
|
#include "datacache/idatacache.h"
|
|
#include "smoke_trail.h"
|
|
#include "collisionutils.h"
|
|
#include "toolframework/itoolframework.h"
|
|
|
|
#ifdef PORTAL2
|
|
#include "ai_criteria.h"
|
|
#endif // PORTAL2
|
|
|
|
// 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;
|
|
}
|
|
};
|
|
|
|
#if 0
|
|
//-----------------------------------------------------------------------------
|
|
// 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 );
|
|
}
|
|
|
|
#endif
|
|
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_KEYFIELD( m_nSkin, FIELD_INTEGER, "ModelSkin" ),
|
|
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_KEYFIELD( m_flModelScale, FIELD_FLOAT, "ModelScale" ),
|
|
DEFINE_FIELD( m_flDissolveStartTime, FIELD_TIME ),
|
|
|
|
// DEFINE_FIELD( m_boneCacheHandle, memhandle_t ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Ignite", InputIgnite ),
|
|
DEFINE_INPUTFUNC( FIELD_FLOAT, "IgniteLifetime", InputIgniteLifetime ),
|
|
|
|
#ifndef HL2_EP3
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "IgniteNumHitboxFires", InputIgnite ),
|
|
DEFINE_INPUTFUNC( FIELD_FLOAT, "IgniteHitboxFireScale", InputIgnite ),
|
|
#endif
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "BecomeRagdoll", InputBecomeRagdoll ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetLightingOriginHack", InputSetLightingOriginRelative ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetLightingOrigin", InputSetLightingOrigin ),
|
|
DEFINE_OUTPUT( m_OnIgnite, "OnIgnite" ),
|
|
|
|
DEFINE_FIELD( m_flFrozen, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_flFrozenThawRate, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_flFrozenMax, FIELD_FLOAT ),
|
|
|
|
DEFINE_FIELD( m_fBoneCacheFlags, FIELD_SHORT ),
|
|
|
|
#ifdef PORTAL2
|
|
DEFINE_OUTPUT( m_OnFizzled, "OnFizzled" ),
|
|
#endif // PORTAL2
|
|
|
|
DEFINE_KEYFIELD( m_bSuppressAnimSounds, FIELD_BOOLEAN, "SuppressAnimSounds" ),
|
|
|
|
END_DATADESC()
|
|
|
|
// Sendtable for fields we don't want to send to clientside animating entities
|
|
BEGIN_SEND_TABLE_NOBASE( CBaseAnimating, DT_ServerAnimationData )
|
|
// ANIMATION_CYCLE_BITS is defined in shareddefs.h
|
|
SendPropFloat (SENDINFO(m_flCycle), ANIMATION_CYCLE_BITS, SPROP_CHANGES_OFTEN|SPROP_ROUNDDOWN, 0.0f, 1.0f)
|
|
END_SEND_TABLE()
|
|
|
|
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) ),
|
|
|
|
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 ),
|
|
SendPropBool( SENDINFO( m_bClientSideRagdoll ) ),
|
|
|
|
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 ) ),
|
|
|
|
SendPropDataTable( "serveranimdata", 0, &REFERENCE_SEND_TABLE( DT_ServerAnimationData ), SendProxy_ClientSideAnimation ),
|
|
|
|
SendPropFloat( SENDINFO( m_flFrozen ) ),
|
|
|
|
SendPropInt( SENDINFO( m_ScaleType ) ),
|
|
|
|
SendPropBool( SENDINFO( m_bSuppressAnimSounds ) )
|
|
|
|
END_SEND_TABLE()
|
|
|
|
|
|
BEGIN_ENT_SCRIPTDESC( CBaseAnimating, CBaseEntity, "Animating models" )
|
|
#ifdef PORTAL2
|
|
DEFINE_SCRIPTFUNC( GetObjectScaleLevel, "The scale size of the entity" )
|
|
#endif // PORTAL2
|
|
DEFINE_SCRIPTFUNC( LookupAttachment, "Get the named attachement id" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetAttachmentOrigin, "GetAttachmentOrigin", "Get the attachement id's origin vector" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetAttachmentAngles, "GetAttachmentAngles", "Get the attachement id's angles as a p,y,r vector" )
|
|
DEFINE_SCRIPTFUNC( IsSequenceFinished, "Ask whether the main sequence is done playing" )
|
|
DEFINE_SCRIPTFUNC( SetBodygroup, "Sets a bodygroup")
|
|
END_SCRIPTDESC();
|
|
|
|
CBaseAnimating::CBaseAnimating()
|
|
{
|
|
m_vecForce.GetForModify().Init();
|
|
m_nForceBone = 0;
|
|
|
|
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;
|
|
SetGlobalFadeScale( 1.0f );
|
|
m_fBoneCacheFlags = 0;
|
|
|
|
if ( m_pBoneMergeCache )
|
|
{
|
|
delete m_pBoneMergeCache;
|
|
m_pBoneMergeCache = NULL;
|
|
}
|
|
|
|
#ifdef PORTAL2
|
|
m_nObjectScaleLevel = 0; // No scale
|
|
m_bCanBeCaptured = true;
|
|
#endif // PORTAL2
|
|
|
|
m_ScaleType = HIERARCHICAL_MODEL_SCALE;
|
|
|
|
// Anything animating is not intended to be cached for depth
|
|
AddEffects( EF_SHADOWDEPTH_NOCACHE );
|
|
}
|
|
|
|
CBaseAnimating::~CBaseAnimating()
|
|
{
|
|
delete m_pBoneMergeCache;
|
|
Studio_DestroyBoneCache( m_boneCacheHandle );
|
|
delete m_pIk;
|
|
InvalidateMdlCache();
|
|
}
|
|
|
|
void CBaseAnimating::Precache()
|
|
{
|
|
#if !defined( TF_DLL ) && !defined ( DOTA_DLL ) && !defined ( PORTAL2 )
|
|
// Anything derived from this class can potentially burn - true, but do we want it to!
|
|
PrecacheParticleSystem( "burning_character" );
|
|
#endif
|
|
|
|
#ifdef PORTAL2
|
|
PrecacheScriptSound( "Prop.Fizzled" );
|
|
#endif // PORTAL2
|
|
|
|
BaseClass::Precache();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Activate!
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::Activate()
|
|
{
|
|
BaseClass::Activate();
|
|
SetLightingOrigin( m_iszLightingOrigin );
|
|
SetLightingOriginRelative( m_iszLightingOriginRelative );
|
|
|
|
#if defined ( PORTAL2 )
|
|
// Scaled physics objects (re)create their physics here
|
|
if ( GetObjectScaleLevel() != 0 && VPhysicsGetObject() )
|
|
{
|
|
// sanity check to make sure 'm_flModelScale' is in sync with the
|
|
// mod specific 'm_nObjectScaleLevel' member.
|
|
Assert( m_flModelScale > 0.0f && m_flModelScale != 1.0f );
|
|
|
|
// UTIL_CreateScaledPhysObject( this, m_flModelScale );
|
|
}
|
|
#endif // PORTAL2
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 );
|
|
LockStudioHdr();
|
|
return result;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::OnRestore()
|
|
{
|
|
BaseClass::OnRestore();
|
|
|
|
if ( m_nSequence != -1 && GetModelPtr() && !IsValidSequence( m_nSequence ) )
|
|
{
|
|
InvalidatePhysicsRecursive( SEQUENCE_CHANGED );
|
|
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, MAX_ANIMTIME_INTERVAL );
|
|
}
|
|
else
|
|
{
|
|
// report actual
|
|
flInterval = clamp( m_flAnimTime - m_flPrevAnimTime, 0, 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 (flNewCycle >= 1.0f)
|
|
{
|
|
ReachedEndOfSequence();
|
|
}
|
|
|
|
if (m_bSequenceLoops)
|
|
{
|
|
// on PPC we can do this truncate without converting to int
|
|
// same as flNewCycle -= (int)(flNewCycle);
|
|
flNewCycle = SubtractIntegerPart(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() );
|
|
|
|
// Msg("%s : %s : %5.1f\n", GetClassname(), GetSequenceName( GetSequence() ), GetCycle() );
|
|
InvalidatePhysicsRecursive( ANIMATION_CHANGED );
|
|
|
|
Studio_InvalidateBoneCacheIfNotMatching( m_boneCacheHandle, gpGlobals->curtime );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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() ) * GetPlaybackRate();
|
|
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 = gpGlobals->curtime - m_flAnimTime;
|
|
flInterval = clamp( flInterval, 0, MAX_ANIMTIME_INTERVAL );
|
|
|
|
//Msg( "%i %s interval %f\n", entindex(), GetClassname(), flInterval );
|
|
if ( flInterval <= 0.001 )
|
|
{
|
|
// Msg("%s : %s : %5.3f (skip)\n", GetClassname(), GetSequenceName( GetSequence() ), GetCycle() );
|
|
return;
|
|
}
|
|
|
|
Thaw( m_flFrozenThawRate * flInterval );
|
|
|
|
// Latch prev
|
|
m_flPrevAnimTime = m_flAnimTime;
|
|
// Set current
|
|
m_flAnimTime = gpGlobals->curtime;
|
|
|
|
// Drive cycle
|
|
float flCycleRate = GetSequenceCycleRate( pStudioHdr, GetSequence() ) * GetPlaybackRate();
|
|
|
|
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;
|
|
}
|
|
#if 0
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
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 );
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
// 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 );
|
|
}
|
|
|
|
int CBaseAnimating::SelectWeightedSequenceFromModifiers( Activity activity, CUtlSymbol *pActivityModifiers, int iModifierCount )
|
|
{
|
|
Assert( activity != ACT_INVALID );
|
|
Assert( GetModelPtr() );
|
|
return GetModelPtr()->SelectWeightedSequenceFromModifiers( activity, pActivityModifiers, iModifierCount );
|
|
}
|
|
|
|
//=========================================================
|
|
// 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 );
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
float CBaseAnimating::GetFirstSequenceAnimTag( int sequence, int nDesiredTag, float flStart, float flEnd )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
return ::GetFirstSequenceAnimTag( GetModelPtr(), sequence, nDesiredTag, flStart, flEnd );
|
|
}
|
|
|
|
float CBaseAnimating::GetAnySequenceAnimTag( int sequence, int nDesiredTag, float flDefault )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
return ::GetAnySequenceAnimTag( GetModelPtr(), sequence, nDesiredTag, flDefault );
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
int CBaseAnimating::LookupSequence( const char *label )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
return ::LookupSequence( GetModelPtr(), label );
|
|
}
|
|
|
|
int CBaseAnimating::LookupSequence( CStudioHdr* pHdr, const char *label )
|
|
{
|
|
Assert( pHdr );
|
|
return ::LookupSequence( pHdr, 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_bClientSideRagdoll = true;
|
|
|
|
// 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_bClientSideRagdoll;
|
|
}
|
|
|
|
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 (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
|
|
{
|
|
DevMsg("ResetSequenceInfo");
|
|
}
|
|
|
|
if (GetSequence() == -1)
|
|
{
|
|
// This shouldn't happen. Setting m_nSequence blindly is a horrible coding practice.
|
|
SetSequence( 0 );
|
|
}
|
|
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
m_flGroundSpeed = GetSequenceGroundSpeed( pStudioHdr, GetSequence() );
|
|
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 ( !pstudiohdr || iSequence < 0 || iSequence >= pstudiohdr->GetNumSeq() )
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CBaseAnimating::SetSequence( int nSequence )
|
|
{
|
|
Assert( GetModelPtr( ) && ( nSequence < GetModelPtr( )->GetNumSeq() ) && ( GetModelPtr( )->GetNumSeq() < (1 << ANIMATION_SEQUENCE_BITS) ) );
|
|
|
|
int oldSequence = m_nSequence;
|
|
m_nSequence = nSequence;
|
|
if ( oldSequence != m_nSequence )
|
|
{
|
|
InvalidatePhysicsRecursive( SEQUENCE_CHANGED );
|
|
OnSequenceSet( oldSequence );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
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 ) * GetPlaybackRate();
|
|
}
|
|
else
|
|
{
|
|
return 1.0;
|
|
}
|
|
}
|
|
|
|
|
|
float CBaseAnimating::GetSequenceGroundSpeed( CStudioHdr *pStudioHdr, int iSequence )
|
|
{
|
|
float t = SequenceDuration( pStudioHdr, iSequence );
|
|
|
|
if (t > 0)
|
|
{
|
|
#if defined( PORTAL2 ) || defined( INFESTED )
|
|
float flBaseSpeed = GetSequenceMoveDist( pStudioHdr, iSequence ) / t;
|
|
return flBaseSpeed * GetModelHierarchyScale() * GetPlaybackRate();
|
|
#else
|
|
return GetSequenceMoveDist( pStudioHdr, iSequence ) / t;
|
|
#endif // PORTAL2 or INFESTED
|
|
}
|
|
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 (GetPlaybackRate() == 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() ) * GetPlaybackRate();
|
|
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 );
|
|
}
|
|
*/
|
|
|
|
event.m_bHandledByScript = eventHandler->HandleScriptedAnimEvent( &event );
|
|
if ( eventHandler->HandleBehaviorAnimEvent( &event ) )
|
|
{
|
|
event.m_bHandledByScript = true;
|
|
}
|
|
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 )
|
|
{
|
|
int nEvent = pEvent->Event();
|
|
|
|
if ((pEvent->type & AE_TYPE_NEWEVENTSYSTEM) && (pEvent->type & AE_TYPE_SERVER))
|
|
{
|
|
if ( nEvent == AE_SV_PLAYSOUND )
|
|
{
|
|
EmitSound( pEvent->options );
|
|
return;
|
|
}
|
|
else if ( nEvent == AE_RAGDOLL )
|
|
{
|
|
// Convert to ragdoll immediately
|
|
BecomeRagdollOnClient( vec3_origin );
|
|
return;
|
|
}
|
|
#ifdef HL2_EPISODIC
|
|
else if ( nEvent == 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
|
|
}
|
|
|
|
// New event, not meant for server. Don't spam console.
|
|
if ((pEvent->type & AE_TYPE_NEWEVENTSYSTEM) && !(pEvent->type & AE_TYPE_SERVER))
|
|
return;
|
|
|
|
if ( pEvent->m_bHandledByScript == true )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Failed to find a handler
|
|
const char *pName = EventList_NameForIndex( nEvent );
|
|
if ( pName)
|
|
{
|
|
DevWarning( 1, "Unhandled animation event %s for %s\n", pName, GetClassname() );
|
|
}
|
|
else
|
|
{
|
|
DevWarning( 1, "Unhandled animation event %d for %s\n", nEvent, 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 )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
|
|
if( !GetModelPtr() )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
//AssertMsg( !Q_stristr( szName, "ValveBiped" ), "ValveBiped bone names are deprecated!" );
|
|
|
|
int ret = Studio_BoneIndexByName( GetModelPtr(), szName );
|
|
|
|
if ( ret == -1 )
|
|
{
|
|
// Try to fix up some common old bone names to new bone names, until I can go through the code and fix all cases or write a data-driven solution.
|
|
if ( Q_stristr( szName, "weapon_bone" ) )
|
|
{
|
|
ret = Studio_BoneIndexByName( GetModelPtr(), "hand_R" );
|
|
}
|
|
else if ( Q_stristr( szName, "Head" ) )
|
|
{
|
|
ret = Studio_BoneIndexByName( GetModelPtr(), "head_0" );
|
|
}
|
|
else if ( Q_stristr( szName, "L_Hand" ) )
|
|
{
|
|
ret = Studio_BoneIndexByName( GetModelPtr(), "hand_L" );
|
|
}
|
|
else if ( Q_stristr( szName, "R_Hand" ) )
|
|
{
|
|
ret = Studio_BoneIndexByName( GetModelPtr(), "hand_R" );
|
|
}
|
|
|
|
//AssertMsg( ret > 0, "Failed to find an alternate bone name!" );
|
|
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
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::GetHitboxBonePosition ( int iBone, Vector &origin, QAngle &angles, QAngle hitboxOrientation )
|
|
{
|
|
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 );
|
|
|
|
matrix3x4_t temp;
|
|
AngleMatrix( hitboxOrientation, temp);
|
|
MatrixMultiply( bonetoworld, temp, temp );
|
|
|
|
MatrixAngles( temp, angles, origin );
|
|
}
|
|
|
|
void CBaseAnimating::GetHitboxBoneTransform( int iBone, QAngle hitboxOrientation, matrix3x4_t &pOut )
|
|
{
|
|
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 );
|
|
|
|
matrix3x4_t temp;
|
|
AngleMatrix( hitboxOrientation, temp);
|
|
MatrixMultiply( bonetoworld, temp, pOut );
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
|
|
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 )
|
|
{
|
|
Assert( false );
|
|
Warning("Uncached query for bone (%d) transform. Please verify that there is an attachment or bounding box associated with this bone.\n", iBone);
|
|
MatrixCopy( EntityToWorldTransform(), pBoneToWorld );
|
|
return;
|
|
}
|
|
|
|
|
|
// 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, 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 )
|
|
{
|
|
SNPROF_ANIM( "CBaseAnimating::CalculateIKLocks" );
|
|
|
|
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, bool bUseSlowHighAccuracyContacts )
|
|
{
|
|
BaseClass::Teleport( newPosition, newAngles, newVelocity, bUseSlowHighAccuracyContacts );
|
|
if (m_pIk)
|
|
{
|
|
m_pIk->ClearTargets( );
|
|
}
|
|
InitStepHeightAdjust();
|
|
}
|
|
|
|
|
|
ConVar sv_pvsskipanimation( "sv_pvsskipanimation", "1", 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" );
|
|
|
|
|
|
|
|
|
|
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( matrix3x4a_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 );
|
|
|
|
BoneVector pos[MAXSTUDIOBONES];
|
|
BoneQuaternionAligned 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 );
|
|
}
|
|
}
|
|
|
|
if ( GetMoveParent() && IsEffectActive(EF_BONEMERGE) )
|
|
{
|
|
CBaseAnimating *pParent = GetMoveParent()->GetBaseAnimating();
|
|
if ( pParent )
|
|
{
|
|
// We're doing bone merging, so do special stuff here.
|
|
CBoneCache *pParentCache = pParent->GetBoneCache();
|
|
if ( pParentCache )
|
|
{
|
|
|
|
if ( !m_pBoneMergeCache )
|
|
{
|
|
m_pBoneMergeCache = new CBoneMergeCache;
|
|
m_pBoneMergeCache->Init( this );
|
|
}
|
|
|
|
m_pBoneMergeCache->BuildMatricesWithBoneMerge(
|
|
pStudioHdr,
|
|
GetAbsAngles(),
|
|
adjOrigin,
|
|
pos,
|
|
q,
|
|
pBoneToWorld,
|
|
pParent,
|
|
pParentCache,
|
|
boneMask );
|
|
|
|
RemoveEFlags( EFL_SETTING_UP_BONES );
|
|
if (ai_setupbones_debug.GetBool())
|
|
{
|
|
DrawRawSkeleton( pBoneToWorld, boneMask, true, 0.11 );
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !IsEffectActive(EF_BONEMERGE) )
|
|
{
|
|
delete m_pBoneMergeCache;
|
|
m_pBoneMergeCache = NULL;
|
|
}
|
|
|
|
Studio_BuildMatrices(
|
|
pStudioHdr,
|
|
GetAbsAngles(),
|
|
adjOrigin,
|
|
pos,
|
|
q,
|
|
-1,
|
|
GetModelHierarchyScale(), // 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)
|
|
const int studioAttachmentNum = Studio_FindAttachment( pStudioHdr, szName );
|
|
|
|
// This is not always a problem; don't assert.
|
|
// AssertMsg3( studioAttachmentNum >= 0, "Couldn't find attachment %s on skeleton %s for object %s\n",
|
|
// szName, pStudioHdr->pszName(), GetDebugName() );
|
|
|
|
return studioAttachmentNum + 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 to vscript caller
|
|
// Input : attachment name
|
|
// Output : location and angles
|
|
//-----------------------------------------------------------------------------
|
|
const Vector& CBaseAnimating::ScriptGetAttachmentOrigin( int iAttachment )
|
|
{
|
|
|
|
static Vector absOrigin;
|
|
static QAngle qa;
|
|
|
|
CBaseAnimating::GetAttachment( iAttachment, absOrigin, qa );
|
|
|
|
return absOrigin;
|
|
}
|
|
|
|
const Vector& CBaseAnimating::ScriptGetAttachmentAngles( int iAttachment )
|
|
{
|
|
|
|
static Vector absOrigin;
|
|
static Vector absAngles;
|
|
static QAngle qa;
|
|
|
|
CBaseAnimating::GetAttachment( iAttachment, absOrigin, qa );
|
|
absAngles.x = qa.x;
|
|
absAngles.y = qa.y;
|
|
absAngles.z = qa.z;
|
|
return absAngles;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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::SetBodygroupPreset( char const *szName )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
|
|
int newBody = m_nBody;
|
|
::SetBodygroupPreset( GetModelPtr( ), newBody, szName );
|
|
m_nBody = newBody;
|
|
}
|
|
|
|
void CBaseAnimating::SetBodygroup( int iGroup, int iValue )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
|
|
int newBody = m_nBody;
|
|
::SetBodygroup( GetModelPtr( ), newBody, iGroup, iValue );
|
|
m_nBody = newBody;
|
|
}
|
|
|
|
int CBaseAnimating::GetBodygroup( int iGroup )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
|
|
return ::GetBodygroup( GetModelPtr( ), m_nBody, iGroup );
|
|
}
|
|
|
|
const char *CBaseAnimating::GetBodygroupName( int iGroup )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
|
|
return ::GetBodygroupName( GetModelPtr( ), iGroup );
|
|
}
|
|
|
|
const char *CBaseAnimating::GetBodygroupPartName( int iGroup, int iPart )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
|
|
return ::GetBodygroupPartName( GetModelPtr( ), iGroup, iPart );
|
|
}
|
|
|
|
int CBaseAnimating::FindBodygroupByName( const char *name )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
|
|
return ::FindBodygroupByName( GetModelPtr( ), name );
|
|
}
|
|
|
|
int CBaseAnimating::GetBodygroupCount( int iGroup )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
|
|
return ::GetBodygroupCount( GetModelPtr( ), iGroup );
|
|
}
|
|
|
|
int CBaseAnimating::GetNumBodyGroups( void )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
|
|
return ::GetNumBodyGroups( GetModelPtr( ) );
|
|
}
|
|
int CBaseAnimating::CountBodyGroupVariants( int group )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
|
|
int numVariants = 0;
|
|
|
|
int count = GetBodygroupCount( group );
|
|
for ( int j=0; j<count; ++j )
|
|
{
|
|
const char *partName = GetBodygroupPartName( group, j );
|
|
|
|
char c = *partName;
|
|
int val = -1;
|
|
if ( c != '\0' )
|
|
{
|
|
val = atoi( partName + 1 );
|
|
}
|
|
if ( val != -1 && c != 'D' )
|
|
{
|
|
++numVariants;
|
|
}
|
|
}
|
|
|
|
return numVariants;
|
|
}
|
|
|
|
/**
|
|
* Find undamaged bodygroup part index
|
|
*/
|
|
int CBaseAnimating::FindBodyGroupVariant( int group, int variant )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
|
|
int numVariants = 0;
|
|
|
|
int count = GetBodygroupCount( group );
|
|
for ( int j=0; j<count; ++j )
|
|
{
|
|
const char *partName = GetBodygroupPartName( group, j );
|
|
|
|
char c = *partName;
|
|
int val = -1;
|
|
if ( c != '\0' )
|
|
{
|
|
val = atoi( partName + 1 );
|
|
}
|
|
if ( val != -1 && c != 'D' )
|
|
{
|
|
++numVariants;
|
|
}
|
|
|
|
if ( variant == numVariants )
|
|
{
|
|
return j;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Find a damaged version of the current part for the given bodygroup
|
|
*/
|
|
int CBaseAnimating::FindDamagedBodyGroupVariant( int group )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
|
|
if ( group < 0 || group >= GetNumBodyGroups() )
|
|
return -1;
|
|
|
|
int current = GetBodygroup( group );
|
|
const char *currentName = GetBodygroupPartName( group, current );
|
|
|
|
CUtlVector< int > damaged;
|
|
int count = GetBodygroupCount( group );
|
|
for ( int j=0; j<count; ++j )
|
|
{
|
|
const char *partName = GetBodygroupPartName( group, j );
|
|
if ( *partName == 'D' && Q_strstr( partName, currentName ) )
|
|
{
|
|
damaged.AddToTail( j );
|
|
}
|
|
}
|
|
|
|
if ( !damaged.Count() )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
return damaged[ RandomInt( 0, damaged.Count()-1 ) ];
|
|
}
|
|
|
|
|
|
void CBaseAnimating::RandomizeBodygroups( CUtlVector< const char * >& groups )
|
|
{
|
|
CUtlVector< int > groupIndex;
|
|
|
|
int i;
|
|
int numVariants = 10000;
|
|
for ( i=0; i<groups.Count(); ++i )
|
|
{
|
|
int index = FindBodygroupByName( groups[i] );
|
|
if ( index < 0 )
|
|
continue;
|
|
|
|
groupIndex.AddToTail( index );
|
|
numVariants = MIN( numVariants, CountBodyGroupVariants( index ) );
|
|
}
|
|
|
|
if ( !numVariants )
|
|
{
|
|
return;
|
|
}
|
|
|
|
int variant = RandomInt( 1, numVariants );
|
|
int partIndex;
|
|
|
|
for ( i=0; i<groupIndex.Count(); ++i )
|
|
{
|
|
partIndex = FindBodyGroupVariant( groupIndex[i], variant );
|
|
if ( partIndex >= 0 )
|
|
{
|
|
SetBodygroup( groupIndex[i], partIndex );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int CBaseAnimating::ExtractBbox( int sequence, Vector& mins, Vector& maxs )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
|
|
return ::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() ) * GetPlaybackRate();
|
|
|
|
Vector vecVelocity;
|
|
Studio_SeqVelocity( pstudiohdr, GetSequence(), flNextCycle, GetPoseParameterArray(), vecVelocity );
|
|
vecVelocity *= GetPlaybackRate();
|
|
|
|
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 * GetPlaybackRate();
|
|
|
|
if ((!m_bSequenceLoops) && flNextCycle > 1.0)
|
|
{
|
|
flIntervalUsed = GetCycle() / (flComputedCycleRate * GetPlaybackRate());
|
|
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();
|
|
|
|
UnlockStudioHdr();
|
|
|
|
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 ) );
|
|
}
|
|
}
|
|
Studio_DestroyBoneCache( m_boneCacheHandle );
|
|
m_boneCacheHandle = 0;
|
|
|
|
UTIL_SetModel( this, szModelName );
|
|
|
|
InvalidateMdlCache();
|
|
|
|
if ( GetModelPtr() )
|
|
{
|
|
InitBoneControllers( );
|
|
|
|
// TODO: what other model data should be initialized?
|
|
SetSequence( 0 );
|
|
}
|
|
|
|
PopulatePoseParameters();
|
|
|
|
/*
|
|
#if defined ( PORTAL2 )
|
|
// After we set our bounds based on the model's default size,
|
|
// scale the bounds based on any starting scale value set in the map.
|
|
if ( GetObjectScaleLevel() != 0 )
|
|
{
|
|
CaptureInfo_t captureInfo;
|
|
UTIL_InitCaptureInfo( captureInfo, this );
|
|
float flModelScale = 1.0f;
|
|
if ( captureInfo.pPlacementQuery )
|
|
{
|
|
flModelScale = captureInfo.pPlacementQuery->GetScaleForStep( GetObjectScaleLevel(), &captureInfo );
|
|
}
|
|
|
|
// Scale by this amount to reach our target scale
|
|
SetModelScale( flModelScale );
|
|
}
|
|
#endif // PORTAL2
|
|
*/
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
matrix3x4a_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_InvalidateBoneCacheIfNotMatching( m_boneCacheHandle, -1.0f );
|
|
}
|
|
|
|
bool CBaseAnimating::TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
|
|
{
|
|
IPhysicsObject *pPhysObject = VPhysicsGetObject();
|
|
// Return a special case for scaled physics objects
|
|
// FIXME: need to have scaled hitbox for scaled models for this to work, we can't assume everything has a VPhysics object
|
|
if ( GetModelHierarchyScale() != 1.0f && pPhysObject )
|
|
{
|
|
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 ( TraceToStudioCsgoHitgroupsPriority( physprops, ray, pStudioHdr, set, hitboxbones, fContentsMask, GetAbsOrigin(), GetModelHierarchyScale(), tr ) )
|
|
{
|
|
mstudiobbox_t *pbox = set->pHitbox( tr.hitbox );
|
|
const mstudiobone_t *pBone = pStudioHdr->pBone(pbox->bone);
|
|
tr.surface.name = "**studio**";
|
|
tr.surface.flags = SURF_HITBOX;
|
|
tr.surface.surfaceProps = pBone->GetSurfaceProp();
|
|
}
|
|
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 );
|
|
m_flEncodedController.Set( iController, 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 );
|
|
}
|
|
}
|
|
}
|
|
|
|
CBaseAnimating* CBaseAnimating::FindFollowedEntity()
|
|
{
|
|
CBaseEntity *follow = GetFollowedEntity();
|
|
|
|
if ( !follow )
|
|
return NULL;
|
|
|
|
if ( follow->IsDormant() )
|
|
return NULL;
|
|
|
|
if ( !follow->GetModel() )
|
|
{
|
|
Warning( "mod_studio: MOVETYPE_FOLLOW with no model.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
if ( modelinfo->GetModelType( follow->GetModel() ) != mod_studio )
|
|
{
|
|
Warning( "Attached %s (mod_studio) to %s (%d)\n",
|
|
modelinfo->GetModelName( GetModel() ),
|
|
modelinfo->GetModelName( follow->GetModel() ),
|
|
modelinfo->GetModelType( follow->GetModel() ) );
|
|
return NULL;
|
|
}
|
|
|
|
return assert_cast< CBaseAnimating* >( follow );
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
|
|
void CBaseAnimating::GetSkeleton( CStudioHdr *pStudioHdr, BoneVector pos[], BoneQuaternionAligned 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)
|
|
{
|
|
int r = 255;
|
|
int g = 255;
|
|
int b = 0;
|
|
|
|
// ----------------
|
|
// Print Look time
|
|
// ----------------
|
|
char tempstr[1024];
|
|
Q_snprintf(tempstr, sizeof(tempstr), "Sequence: (%3d) %s",GetSequence(), GetSequenceName( GetSequence() ) );
|
|
EntityText(text_offset,tempstr,0,r,g,b);
|
|
text_offset++;
|
|
const char *pActname = GetSequenceActivityName(GetSequence());
|
|
if ( pActname && strlen(pActname) )
|
|
{
|
|
Q_snprintf(tempstr, sizeof(tempstr), "Activity %s", pActname );
|
|
EntityText(text_offset,tempstr,0,r,g,b);
|
|
text_offset++;
|
|
}
|
|
|
|
Q_snprintf(tempstr, sizeof(tempstr), "Cycle: %.5f (%.5f)", (float)GetCycle(), m_flAnimTime.Get() );
|
|
EntityText(text_offset,tempstr,0,r,g,b);
|
|
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:
|
|
// 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*/ )
|
|
{
|
|
MDLCACHE_CRITICAL_SECTION();
|
|
|
|
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 );
|
|
|
|
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] );
|
|
}
|
|
|
|
if ( pbox->flCapsuleRadius > 0 )
|
|
{
|
|
matrix3x4_t temp;
|
|
GetHitboxBoneTransform( pbox->bone, pbox->angOffsetOrientation, temp );
|
|
|
|
Vector vecCapsuleCenters[ 2 ];
|
|
VectorTransform( pbox->bbmin, temp, vecCapsuleCenters[0] );
|
|
VectorTransform( pbox->bbmax, temp, vecCapsuleCenters[1] );
|
|
|
|
NDebugOverlay::Capsule( vecCapsuleCenters[0], vecCapsuleCenters[1], pbox->flCapsuleRadius, r, g, b, 255, duration );
|
|
}
|
|
else
|
|
{
|
|
GetHitboxBonePosition( pbox->bone, position, angles, pbox->angOffsetOrientation );
|
|
NDebugOverlay::BoxAngles( position, pbox->bbmin*GetModelHierarchyScale(), pbox->bbmax*GetModelHierarchyScale(), 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*GetModelHierarchyScale(), pbox->bbmax*GetModelHierarchyScale(), 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;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Computes a box that surrounds a single hitboxes, in entity space
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseAnimating::ComputeHitboxSurroundingBox( int iHitbox, 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 || iHitbox < 0 || iHitbox >= set->numhitboxes )
|
|
return false;
|
|
|
|
CBoneCache *pCache = GetBoneCache();
|
|
|
|
mstudiobbox_t *pbox = set->pHitbox( iHitbox );
|
|
matrix3x4_t *pMatrix = pCache->GetCachedBone(pbox->bone);
|
|
|
|
if ( !pMatrix )
|
|
return false;
|
|
|
|
TransformAABB( *pMatrix, pbox->bbmin, pbox->bbmax, *pVecWorldMins, *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() | EF_NOINTERP );
|
|
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*/, ModelScaleType_t scaleType /*= HIERARCHICAL_MODEL_SCALE*/ )
|
|
{
|
|
SetModelScaleType( scaleType );
|
|
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;
|
|
if ( HasDataObjectType( MODELSCALE ) )
|
|
{
|
|
DestroyDataObject( MODELSCALE );
|
|
}
|
|
}
|
|
}
|
|
|
|
ModelScaleType_t CBaseAnimating::GetModelScaleType() const
|
|
{
|
|
return m_ScaleType;
|
|
}
|
|
|
|
void CBaseAnimating::SetModelScaleType( ModelScaleType_t scaleType )
|
|
{
|
|
m_ScaleType = scaleType;
|
|
}
|
|
|
|
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 );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : float
|
|
//-----------------------------------------------------------------------------
|
|
float CBaseAnimating::GetModelScale() const
|
|
{
|
|
return m_flModelScale;
|
|
}
|
|
|
|
float CBaseAnimating::GetModelHierarchyScale()
|
|
{
|
|
CStudioHdr* pHdr = GetModelPtr();
|
|
return ( GetModelScaleType() == HIERARCHICAL_MODEL_SCALE || (pHdr && pHdr->numbones() == 1) ) ? m_flModelScale : 1.0f;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
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, flFlameLifetime, flSize );
|
|
AddFlag( FL_ONFIRE );
|
|
SetEffectEntity( pFlame );
|
|
|
|
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::IgniteUseCheapEffect( bool bUseCheapEffect )
|
|
{
|
|
if( !IsOnFire() )
|
|
{
|
|
Ignite( 30, false, 0.0f, true );
|
|
}
|
|
|
|
CEntityFlame *pFlame = dynamic_cast<CEntityFlame*>( GetEffectEntity() );
|
|
|
|
if ( !pFlame )
|
|
return;
|
|
|
|
pFlame->UseCheapEffect( bUseCheapEffect );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 );
|
|
}
|
|
}
|
|
|
|
#ifdef PORTAL2
|
|
EmitSound( "Prop.Fizzled" );
|
|
#endif // PORTAL2
|
|
|
|
return bRagdollCreated;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Make a model look as though it's burning.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::Scorch( int rate, int floor )
|
|
{
|
|
color24 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::InputBecomeRagdoll( inputdata_t &inputdata )
|
|
{
|
|
BecomeRagdollOnClient( vec3_origin );
|
|
}
|
|
|
|
void CBaseAnimating::Thaw( float flThawAmount )
|
|
{
|
|
#if defined( HL2_EP3 ) || defined( INFESTED_DLL )
|
|
if ( m_flFrozen <= 0.0f )
|
|
return;
|
|
|
|
bool bWasFrozen = IsFrozen();
|
|
|
|
CEntityFreezing *pFreezing = NULL;
|
|
|
|
if ( ( GetFlags() & FL_FREEZING ) != 0 )
|
|
{
|
|
// Get the freezing effect
|
|
pFreezing = dynamic_cast<CEntityFreezing*>( GetEffectEntity() );
|
|
}
|
|
|
|
float fTotalFrozen = 0.0f;
|
|
|
|
if ( pFreezing )
|
|
{
|
|
studiohdr_t *pStudioHdr = modelinfo->GetStudiomodel( GetModel() );
|
|
if ( pStudioHdr )
|
|
{
|
|
// Thaw all hitboxes
|
|
mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( GetHitboxSet() );
|
|
if ( set && set->numhitboxes > 0 )
|
|
{
|
|
for ( int i = 0; i < set->numhitboxes; ++i )
|
|
{
|
|
pFreezing->m_flFrozenPerHitbox.GetForModify( i ) = MAX( 0.0f, pFreezing->m_flFrozenPerHitbox[ i ] - flThawAmount );
|
|
}
|
|
|
|
fTotalFrozen /= set->numhitboxes;
|
|
}
|
|
}
|
|
}
|
|
|
|
float flNewFrozen;
|
|
|
|
if ( fTotalFrozen )
|
|
{
|
|
// Total frozen amount from hitboxes
|
|
flNewFrozen = MAX( 0.0f, fTotalFrozen * 2.0f );
|
|
}
|
|
else
|
|
{
|
|
// Not hitboxes frozen, so do the thawing directly
|
|
flNewFrozen = MAX( 0.0f, m_flFrozen - flThawAmount );
|
|
}
|
|
|
|
m_flAttackFrozen = MIN( m_flAttackFrozen, flNewFrozen );
|
|
m_flMovementFrozen = MIN( m_flMovementFrozen, flNewFrozen );
|
|
m_flFrozen = flNewFrozen;
|
|
|
|
if ( bWasFrozen && !IsFrozen() )
|
|
{
|
|
// We're not in a frozen state anymore!
|
|
Unfreeze();
|
|
}
|
|
|
|
if ( pFreezing )
|
|
{
|
|
if ( m_flFrozen > 0.0f )
|
|
{
|
|
// Update our freezing effect
|
|
pFreezing->SetFrozen( m_flFrozen );
|
|
}
|
|
else
|
|
{
|
|
// Remove the freezing effect
|
|
UTIL_Remove( pFreezing );
|
|
SetEffectEntity( NULL );
|
|
RemoveFlag( FL_FREEZING );
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Debug function to make this base animating freeze in place (or unfreeze).
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::ToggleFreeze()
|
|
{
|
|
if ( !IsFrozen() )
|
|
{
|
|
Freeze();
|
|
}
|
|
else
|
|
{
|
|
Unfreeze();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Freezes this NPC
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::Freeze( float flFreezeAmount, CBaseEntity *pFreezer, Ray_t *pFreezeRay )
|
|
{
|
|
#if defined( HL2_EP3 ) || defined( INFESTED_DLL )
|
|
if ( flFreezeAmount < 0 )
|
|
{
|
|
// This is a debugging freeze
|
|
m_flFrozen = 1.0f;
|
|
m_flFrozenThawRate = 0.0f;
|
|
return;
|
|
}
|
|
|
|
// Bail if it's not allowed to freeze
|
|
if ( m_flFrozenMax < 0.0f )
|
|
return;
|
|
|
|
CEntityFreezing *pFreezing = NULL;
|
|
|
|
if ( ( GetFlags() & FL_FREEZING ) != 0 )
|
|
{
|
|
pFreezing = dynamic_cast<CEntityFreezing*>( GetEffectEntity() );
|
|
}
|
|
else
|
|
{
|
|
pFreezing = CEntityFreezing::Create( this );
|
|
SetEffectEntity( pFreezing );
|
|
AddFlag( FL_FREEZING );
|
|
}
|
|
|
|
if ( !pFreezing )
|
|
{
|
|
return;
|
|
}
|
|
|
|
float fMaxFrozen = ( m_flFrozenMax == 0.0f ) ? ( 1.0f ) : m_flFrozenMax;
|
|
|
|
if ( pFreezeRay )
|
|
{
|
|
float fTotalFrozen = 0.0f;
|
|
m_flMovementFrozen = 0.0f;
|
|
m_flAttackFrozen = 0.0f;
|
|
|
|
float flMidHeight = WorldSpaceCenter().z;
|
|
|
|
studiohdr_t *pStudioHdr = modelinfo->GetStudiomodel( GetModel() );
|
|
if ( pStudioHdr )
|
|
{
|
|
// Freeze hitboxes that intersect this ray
|
|
mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( GetHitboxSet() );
|
|
if ( set && set->numhitboxes > 0 )
|
|
{
|
|
for ( int i = 0; i < set->numhitboxes; ++i )
|
|
{
|
|
// Get the hitbox data
|
|
mstudiobbox_t *pBox = set->pHitbox(i);
|
|
|
|
Vector vecPosition;
|
|
QAngle angAngles;
|
|
GetBonePosition( pBox->bone, vecPosition, angAngles );
|
|
|
|
trace_t tr;
|
|
if ( IntersectRayWithOBB( *pFreezeRay, vecPosition, angAngles, pBox->bbmin * GetModelHierarchyScale(), pBox->bbmax * GetModelHierarchyScale(), 0.0f, &tr ) )
|
|
{
|
|
// Ice ray intersected this bounding box
|
|
pFreezing->m_flFrozenPerHitbox.GetForModify( i ) = MIN( 1.0f, pFreezing->m_flFrozenPerHitbox[ i ] + flFreezeAmount );
|
|
}
|
|
|
|
fTotalFrozen += pFreezing->m_flFrozenPerHitbox[ i ];
|
|
|
|
// If it's above their middle prevent attacking otherwise prevent movement
|
|
if ( vecPosition.z > flMidHeight )
|
|
{
|
|
m_flAttackFrozen += pFreezing->m_flFrozenPerHitbox[ i ];
|
|
}
|
|
else
|
|
{
|
|
m_flMovementFrozen += pFreezing->m_flFrozenPerHitbox[ i ];
|
|
}
|
|
}
|
|
|
|
fTotalFrozen /= set->numhitboxes;
|
|
m_flMovementFrozen /= set->numhitboxes;
|
|
m_flAttackFrozen /= set->numhitboxes;
|
|
}
|
|
}
|
|
|
|
m_flFrozen = MIN( fMaxFrozen, fTotalFrozen * 3.0f );
|
|
m_flMovementFrozen = MIN( m_flFrozen, m_flMovementFrozen * 3.0f );
|
|
m_flAttackFrozen = MIN( m_flFrozen, m_flAttackFrozen * 3.0f );
|
|
}
|
|
else
|
|
{
|
|
studiohdr_t *pStudioHdr = GetModel() ? modelinfo->GetStudiomodel( GetModel() ) : NULL;
|
|
if ( pStudioHdr )
|
|
{
|
|
// Freeze all hitboxes
|
|
mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( GetHitboxSet() );
|
|
if ( set )
|
|
{
|
|
for ( int i = 0; i < set->numhitboxes; ++i )
|
|
{
|
|
pFreezing->m_flFrozenPerHitbox.GetForModify( i ) = MIN( 1.0f, pFreezing->m_flFrozenPerHitbox[ i ] + flFreezeAmount );
|
|
}
|
|
}
|
|
}
|
|
|
|
m_flFrozen = MIN( fMaxFrozen, m_flFrozen + flFreezeAmount );
|
|
}
|
|
|
|
pFreezing->SetFrozen( m_flFrozen );
|
|
#endif
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseAnimating::Unfreeze()
|
|
{
|
|
if ( m_flFrozenThawRate < 0.0f )
|
|
{
|
|
// It's never going to thaw, so jump it back to zero
|
|
m_flFrozen = 0.0f;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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;
|
|
}
|
|
|
|
#ifdef PORTAL2
|
|
void CBaseAnimating::OnFizzled( void )
|
|
{
|
|
m_OnFizzled.FireOutput( this, this );
|
|
}
|
|
#endif // PORTAL2
|