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