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.
1166 lines
30 KiB
1166 lines
30 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//===========================================================================// |
|
|
|
#include "cbase.h" |
|
#include "animation.h" |
|
#include "studio.h" |
|
#include "bone_setup.h" |
|
#include "ai_basenpc.h" |
|
#include "npcevent.h" |
|
|
|
#include "saverestore_utlvector.h" |
|
#include "dt_utlvector_send.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
extern ConVar ai_sequence_debug; |
|
|
|
|
|
BEGIN_SIMPLE_DATADESC( CAnimationLayer ) |
|
|
|
// DEFINE_FIELD( m_pOwnerEntity, CBaseAnimatingOverlay ), |
|
DEFINE_FIELD( m_fFlags, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_bSequenceFinished, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_bLooping, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_nSequence, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_flCycle, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flPrevCycle, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flPlaybackRate, FIELD_FLOAT), |
|
DEFINE_FIELD( m_flWeight, FIELD_FLOAT), |
|
DEFINE_FIELD( m_flBlendIn, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flBlendOut, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flKillRate, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flKillDelay, FIELD_FLOAT ), |
|
DEFINE_CUSTOM_FIELD( m_nActivity, ActivityDataOps() ), |
|
DEFINE_FIELD( m_nPriority, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_nOrder, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_flLastEventCheck, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flLastAccess, FIELD_TIME ), |
|
DEFINE_FIELD( m_flLayerAnimtime, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flLayerFadeOuttime, FIELD_FLOAT ), |
|
|
|
END_DATADESC() |
|
|
|
|
|
BEGIN_DATADESC( CBaseAnimatingOverlay ) |
|
|
|
DEFINE_UTLVECTOR( m_AnimOverlay, FIELD_EMBEDDED ), |
|
|
|
// DEFINE_FIELD( m_nActiveLayers, FIELD_INTEGER ), |
|
// DEFINE_FIELD( m_nActiveBaseLayers, FIELD_INTEGER ), |
|
|
|
END_DATADESC() |
|
|
|
|
|
#define ORDER_BITS 4 |
|
#define WEIGHT_BITS 8 |
|
|
|
BEGIN_SEND_TABLE_NOBASE(CAnimationLayer, DT_Animationlayer) |
|
SendPropInt (SENDINFO(m_nSequence), ANIMATION_SEQUENCE_BITS,SPROP_UNSIGNED), |
|
SendPropFloat (SENDINFO(m_flCycle), ANIMATION_CYCLE_BITS, SPROP_ROUNDDOWN, 0.0f, 1.0f), |
|
SendPropFloat (SENDINFO(m_flPrevCycle), ANIMATION_CYCLE_BITS, SPROP_ROUNDDOWN, 0.0f, 1.0f), |
|
SendPropFloat (SENDINFO(m_flWeight), WEIGHT_BITS, 0, 0.0f, 1.0f), |
|
SendPropInt (SENDINFO(m_nOrder), ORDER_BITS, SPROP_UNSIGNED), |
|
END_SEND_TABLE() |
|
|
|
|
|
BEGIN_SEND_TABLE_NOBASE( CBaseAnimatingOverlay, DT_OverlayVars ) |
|
SendPropUtlVector( |
|
SENDINFO_UTLVECTOR( m_AnimOverlay ), |
|
CBaseAnimatingOverlay::MAX_OVERLAYS, // max elements |
|
SendPropDataTable( NULL, 0, &REFERENCE_SEND_TABLE( DT_Animationlayer ) ) ) |
|
END_SEND_TABLE() |
|
|
|
|
|
IMPLEMENT_SERVERCLASS_ST( CBaseAnimatingOverlay, DT_BaseAnimatingOverlay ) |
|
// These are in their own separate data table so CCSPlayer can exclude all of these. |
|
SendPropDataTable( "overlay_vars", 0, &REFERENCE_SEND_TABLE( DT_OverlayVars ) ) |
|
END_SEND_TABLE() |
|
|
|
|
|
|
|
|
|
CAnimationLayer::CAnimationLayer( ) |
|
{ |
|
Init( NULL ); |
|
} |
|
|
|
|
|
void CAnimationLayer::Init( CBaseAnimatingOverlay *pOverlay ) |
|
{ |
|
m_pOwnerEntity = pOverlay; |
|
m_fFlags = 0; |
|
m_flWeight = 0; |
|
m_flCycle = 0; |
|
m_flPrevCycle = 0; |
|
m_bSequenceFinished = false; |
|
m_nActivity = ACT_INVALID; |
|
m_nSequence = 0; |
|
m_nPriority = 0; |
|
m_nOrder.Set( CBaseAnimatingOverlay::MAX_OVERLAYS ); |
|
|
|
m_flBlendIn = 0.0; |
|
m_flBlendOut = 0.0; |
|
|
|
m_flKillRate = 100.0; |
|
m_flKillDelay = 0.0; |
|
m_flPlaybackRate = 1.0; |
|
m_flLastEventCheck = 0.0; |
|
m_flLastAccess = gpGlobals->curtime; |
|
m_flLayerAnimtime = 0; |
|
m_flLayerFadeOuttime = 0; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
|
|
void CAnimationLayer::StudioFrameAdvance( float flInterval, CBaseAnimating *pOwner ) |
|
{ |
|
float flCycleRate = pOwner->GetSequenceCycleRate( m_nSequence ); |
|
|
|
m_flPrevCycle = m_flCycle; |
|
m_flCycle += flInterval * flCycleRate * m_flPlaybackRate; |
|
|
|
if (m_flCycle < 0.0) |
|
{ |
|
if (m_bLooping) |
|
{ |
|
m_flCycle -= (int)(m_flCycle); |
|
} |
|
else |
|
{ |
|
m_flCycle = 0; |
|
} |
|
} |
|
else if (m_flCycle >= 1.0) |
|
{ |
|
m_bSequenceFinished = true; |
|
|
|
if (m_bLooping) |
|
{ |
|
m_flCycle -= (int)(m_flCycle); |
|
} |
|
else |
|
{ |
|
m_flCycle = 1.0; |
|
} |
|
} |
|
|
|
if (IsAutoramp()) |
|
{ |
|
m_flWeight = 1; |
|
|
|
// blend in? |
|
if ( m_flBlendIn != 0.0f ) |
|
{ |
|
if (m_flCycle < m_flBlendIn) |
|
{ |
|
m_flWeight = m_flCycle / m_flBlendIn; |
|
} |
|
} |
|
|
|
// blend out? |
|
if ( m_flBlendOut != 0.0f ) |
|
{ |
|
if (m_flCycle > 1.0 - m_flBlendOut) |
|
{ |
|
m_flWeight = (1.0 - m_flCycle) / m_flBlendOut; |
|
} |
|
} |
|
|
|
m_flWeight = 3.0 * m_flWeight * m_flWeight - 2.0 * m_flWeight * m_flWeight * m_flWeight; |
|
if (m_nSequence == 0) |
|
m_flWeight = 0; |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
|
|
bool CAnimationLayer::IsAbandoned( void ) |
|
{ |
|
if (IsActive() && !IsAutokill() && !IsKillMe() && m_flLastAccess > 0.0 && (gpGlobals->curtime - m_flLastAccess > 0.2)) |
|
return true; |
|
else |
|
return false; |
|
} |
|
|
|
void CAnimationLayer::MarkActive( void ) |
|
{ |
|
m_flLastAccess = gpGlobals->curtime; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
|
|
void CBaseAnimatingOverlay::VerifyOrder( void ) |
|
{ |
|
#ifdef _DEBUG |
|
int i, j; |
|
// test sorting of the layers |
|
int layer[MAX_OVERLAYS]; |
|
int maxOrder = -1; |
|
for (i = 0; i < MAX_OVERLAYS; i++) |
|
{ |
|
layer[i] = MAX_OVERLAYS; |
|
} |
|
for (i = 0; i < m_AnimOverlay.Count(); i++) |
|
{ |
|
if (m_AnimOverlay[ i ].m_nOrder < MAX_OVERLAYS) |
|
{ |
|
j = m_AnimOverlay[ i ].m_nOrder; |
|
Assert( layer[j] == MAX_OVERLAYS ); |
|
layer[j] = i; |
|
if (j > maxOrder) |
|
maxOrder = j; |
|
} |
|
} |
|
|
|
// make sure they're sequential |
|
// Aim layers are allowed to have gaps, and we are moving aim blending to server |
|
// for ( i = 0; i <= maxOrder; i++ ) |
|
// { |
|
// Assert( layer[i] != MAX_OVERLAYS); |
|
// } |
|
|
|
/* |
|
for ( i = 0; i < MAX_OVERLAYS; i++ ) |
|
{ |
|
int j = layer[i]; |
|
if (j != MAX_OVERLAYS) |
|
{ |
|
char tempstr[512]; |
|
Q_snprintf( tempstr, sizeof( tempstr ),"%d : %d :%.2f :%d:%d:%.1f", |
|
j, |
|
m_AnimOverlay[ j ].m_nSequence, |
|
m_AnimOverlay[ j ].m_flWeight, |
|
m_AnimOverlay[ j ].IsActive(), |
|
m_AnimOverlay[ j ].IsKillMe(), |
|
m_AnimOverlay[ j ].m_flKillDelay |
|
); |
|
EntityText( i, tempstr, 0.1 ); |
|
} |
|
} |
|
*/ |
|
#endif |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : advance the animation frame up to the current time |
|
// if an flInterval is passed in, only advance animation that number of seconds |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
|
|
void CBaseAnimatingOverlay::StudioFrameAdvance () |
|
{ |
|
float flAdvance = GetAnimTimeInterval(); |
|
|
|
VerifyOrder(); |
|
|
|
BaseClass::StudioFrameAdvance(); |
|
|
|
for ( int i = 0; i < m_AnimOverlay.Count(); i++ ) |
|
{ |
|
CAnimationLayer *pLayer = &m_AnimOverlay[i]; |
|
|
|
if (pLayer->IsActive()) |
|
{ |
|
// Assert( !m_AnimOverlay[ i ].IsAbandoned() ); |
|
if (pLayer->IsKillMe()) |
|
{ |
|
if (pLayer->m_flKillDelay > 0) |
|
{ |
|
pLayer->m_flKillDelay -= flAdvance; |
|
pLayer->m_flKillDelay = clamp( pLayer->m_flKillDelay, 0.0f, 1.0f ); |
|
} |
|
else if (pLayer->m_flWeight != 0.0f) |
|
{ |
|
// give it at least one frame advance cycle to propagate 0.0 to client |
|
pLayer->m_flWeight -= pLayer->m_flKillRate * flAdvance; |
|
pLayer->m_flWeight = clamp( (float) pLayer->m_flWeight, 0.0f, 1.0f ); |
|
} |
|
else |
|
{ |
|
// shift the other layers down in order |
|
if (ai_sequence_debug.GetBool() == true && m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) |
|
{ |
|
Msg("removing %d (%d): %s : %5.3f (%.3f)\n", i, pLayer->m_nOrder.Get(), GetSequenceName( pLayer->m_nSequence ), pLayer->m_flCycle.Get(), pLayer->m_flWeight.Get() ); |
|
} |
|
FastRemoveLayer( i ); |
|
// needs at least one thing cycle dead to trigger sequence change |
|
pLayer->Dying(); |
|
continue; |
|
} |
|
} |
|
|
|
pLayer->StudioFrameAdvance( flAdvance, this ); |
|
if ( pLayer->m_bSequenceFinished && (pLayer->IsAutokill()) ) |
|
{ |
|
pLayer->m_flWeight = 0.0f; |
|
pLayer->KillMe(); |
|
} |
|
} |
|
else if (pLayer->IsDying()) |
|
{ |
|
pLayer->Dead(); |
|
} |
|
else if (pLayer->m_flWeight > 0.0) |
|
{ |
|
// Now that the server blends, it is turning off layers all the time. Having a weight left over |
|
// when you're no longer marked as active is now harmless and commonplace. Just clean up. |
|
pLayer->Init( this ); |
|
pLayer->Dying(); |
|
} |
|
} |
|
|
|
if (ai_sequence_debug.GetBool() == true && m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) |
|
{ |
|
for ( int i = 0; i < m_AnimOverlay.Count(); i++ ) |
|
{ |
|
if (m_AnimOverlay[ i ].IsActive()) |
|
{ |
|
/* |
|
if (m_AnimOverlay[ i ].IsAbandoned()) |
|
{ |
|
Msg(" %d abandoned %.2f (%.2f)\n", i, gpGlobals->curtime, m_AnimOverlay[ i ].m_flLastAccess ); |
|
} |
|
*/ |
|
Msg(" %d (%d): %s : %5.3f (%.3f)\n", i, m_AnimOverlay[ i ].m_nOrder.Get(), GetSequenceName( m_AnimOverlay[ i ].m_nSequence ), m_AnimOverlay[ i ].m_flCycle.Get(), m_AnimOverlay[ i ].m_flWeight.Get() ); |
|
} |
|
} |
|
} |
|
|
|
VerifyOrder(); |
|
} |
|
|
|
|
|
|
|
//========================================================= |
|
// DispatchAnimEvents |
|
//========================================================= |
|
void CBaseAnimatingOverlay::DispatchAnimEvents ( CBaseAnimating *eventHandler ) |
|
{ |
|
BaseClass::DispatchAnimEvents( eventHandler ); |
|
|
|
for ( int i = 0; i < m_AnimOverlay.Count(); i++ ) |
|
{ |
|
if (m_AnimOverlay[ i ].IsActive()) |
|
{ |
|
m_AnimOverlay[ i ].DispatchAnimEvents( eventHandler, this ); |
|
} |
|
} |
|
} |
|
|
|
void CAnimationLayer::DispatchAnimEvents( CBaseAnimating *eventHandler, CBaseAnimating *pOwner ) |
|
{ |
|
animevent_t event; |
|
|
|
CStudioHdr *pstudiohdr = pOwner->GetModelPtr( ); |
|
|
|
if ( !pstudiohdr ) |
|
{ |
|
Assert(!"CBaseAnimating::DispatchAnimEvents: model missing"); |
|
return; |
|
} |
|
|
|
if ( !pstudiohdr->SequencesAvailable() ) |
|
{ |
|
return; |
|
} |
|
|
|
if ( m_nSequence >= pstudiohdr->GetNumSeq() ) |
|
return; |
|
|
|
// don't fire if here are no events |
|
if ( pstudiohdr->pSeqdesc( m_nSequence ).numevents == 0 ) |
|
{ |
|
return; |
|
} |
|
|
|
// look from when it last checked to some short time in the future |
|
float flCycleRate = pOwner->GetSequenceCycleRate( m_nSequence ) * m_flPlaybackRate; |
|
float flStart = m_flLastEventCheck; |
|
float flEnd = m_flCycle; |
|
|
|
if (!m_bLooping) |
|
{ |
|
// fire off events early |
|
float flLastVisibleCycle = 1.0f - (pstudiohdr->pSeqdesc( m_nSequence ).fadeouttime) * flCycleRate; |
|
if (flEnd >= flLastVisibleCycle || flEnd < 0.0) |
|
{ |
|
m_bSequenceFinished = true; |
|
flEnd = 1.0f; |
|
} |
|
} |
|
m_flLastEventCheck = flEnd; |
|
|
|
/* |
|
if (pOwner->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) |
|
{ |
|
Msg( "%s:%s : checking %.2f %.2f (%d)\n", STRING(pOwner->GetModelName()), pstudiohdr->pSeqdesc( m_nSequence ).pszLabel(), flStart, flEnd, m_bSequenceFinished ); |
|
} |
|
*/ |
|
|
|
// FIXME: does not handle negative framerates! |
|
int index = 0; |
|
while ( (index = GetAnimationEvent( pstudiohdr, m_nSequence, &event, flStart, flEnd, index ) ) != 0 ) |
|
{ |
|
event.pSource = pOwner; |
|
// calc when this event should happen |
|
if (flCycleRate > 0.0) |
|
{ |
|
float flCycle = event.cycle; |
|
if (flCycle > m_flCycle) |
|
{ |
|
flCycle = flCycle - 1.0; |
|
} |
|
event.eventtime = pOwner->m_flAnimTime + (flCycle - m_flCycle) / flCycleRate + pOwner->GetAnimTimeInterval(); |
|
} |
|
|
|
// Msg( "dispatch %d (%d : %.2f)\n", index - 1, event.event, event.eventtime ); |
|
eventHandler->HandleAnimEvent( &event ); |
|
} |
|
} |
|
|
|
|
|
|
|
void CBaseAnimatingOverlay::GetSkeleton( CStudioHdr *pStudioHdr, Vector pos[], Quaternion q[], int boneMask ) |
|
{ |
|
if(!pStudioHdr) |
|
{ |
|
Assert(!"CBaseAnimating::GetSkeleton() without a model"); |
|
return; |
|
} |
|
|
|
if (!pStudioHdr->SequencesAvailable()) |
|
{ |
|
return; |
|
} |
|
|
|
IBoneSetup boneSetup( pStudioHdr, boneMask, GetPoseParameterArray() ); |
|
boneSetup.InitPose( pos, q ); |
|
|
|
boneSetup.AccumulatePose( pos, q, GetSequence(), GetCycle(), 1.0, gpGlobals->curtime, m_pIk ); |
|
|
|
// sort the layers |
|
int layer[MAX_OVERLAYS] = {}; |
|
int i; |
|
for (i = 0; i < m_AnimOverlay.Count(); i++) |
|
{ |
|
layer[i] = MAX_OVERLAYS; |
|
} |
|
for (i = 0; i < m_AnimOverlay.Count(); i++) |
|
{ |
|
CAnimationLayer &pLayer = m_AnimOverlay[i]; |
|
if( (pLayer.m_flWeight > 0) && pLayer.IsActive() && pLayer.m_nOrder >= 0 && pLayer.m_nOrder < m_AnimOverlay.Count()) |
|
{ |
|
layer[pLayer.m_nOrder] = i; |
|
} |
|
} |
|
for (i = 0; i < m_AnimOverlay.Count(); i++) |
|
{ |
|
if (layer[i] >= 0 && layer[i] < m_AnimOverlay.Count()) |
|
{ |
|
CAnimationLayer &pLayer = m_AnimOverlay[layer[i]]; |
|
// UNDONE: Is it correct to use overlay weight for IK too? |
|
boneSetup.AccumulatePose( pos, q, pLayer.m_nSequence, pLayer.m_flCycle, pLayer.m_flWeight, 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() ); |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: zero's out all non-restore safe fields |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void CBaseAnimatingOverlay::OnRestore( ) |
|
{ |
|
int i; |
|
|
|
// force order of unused layers to current MAX_OVERLAYS |
|
// and Tracker 48843 (Alyx skating after restore) restore the owner entity ptr (otherwise the network layer won't get NetworkStateChanged signals until the layer is re-Init()'ed |
|
for (i = 0; i < m_AnimOverlay.Count(); i++) |
|
{ |
|
m_AnimOverlay[i].m_pOwnerEntity = this; |
|
|
|
if ( !m_AnimOverlay[i].IsActive()) |
|
{ |
|
m_AnimOverlay[i].m_nOrder.Set( MAX_OVERLAYS ); |
|
} |
|
} |
|
|
|
// get rid of all layers that shouldn't be restored |
|
for (i = 0; i < m_AnimOverlay.Count(); i++) |
|
{ |
|
if ( ( m_AnimOverlay[i].IsActive() && (m_AnimOverlay[i].m_fFlags & ANIM_LAYER_DONTRESTORE) ) || |
|
( GetModelPtr() && !IsValidSequence(m_AnimOverlay[i].m_nSequence) ) ) |
|
{ |
|
FastRemoveLayer( i ); |
|
} |
|
} |
|
|
|
BaseClass::OnRestore(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int CBaseAnimatingOverlay::AddGestureSequence( int sequence, bool autokill /*= true*/ ) |
|
{ |
|
int i = AddLayeredSequence( sequence, 0 ); |
|
// No room? |
|
if ( IsValidLayer( i ) ) |
|
{ |
|
SetLayerAutokill( i, autokill ); |
|
} |
|
|
|
return i; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int CBaseAnimatingOverlay::AddGestureSequence( int nSequence, float flDuration, bool autokill /*= true*/ ) |
|
{ |
|
int iLayer = AddGestureSequence( nSequence, autokill ); |
|
Assert( iLayer != -1 ); |
|
|
|
if (iLayer >= 0 && flDuration > 0) |
|
{ |
|
m_AnimOverlay[iLayer].m_flPlaybackRate = SequenceDuration( nSequence ) / flDuration; |
|
} |
|
return iLayer; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
int CBaseAnimatingOverlay::AddGesture( Activity activity, bool autokill /*= true*/ ) |
|
{ |
|
if ( IsPlayingGesture( activity ) ) |
|
{ |
|
return FindGestureLayer( activity ); |
|
} |
|
|
|
int seq = SelectWeightedSequence( activity ); |
|
if ( seq <= 0 ) |
|
{ |
|
const char *actname = CAI_BaseNPC::GetActivityName( activity ); |
|
DevMsg( "CBaseAnimatingOverlay::AddGesture: model %s missing activity %s\n", STRING(GetModelName()), actname ); |
|
return -1; |
|
} |
|
|
|
int i = AddGestureSequence( seq, autokill ); |
|
Assert( i != -1 ); |
|
if ( i != -1 ) |
|
{ |
|
m_AnimOverlay[ i ].m_nActivity = activity; |
|
} |
|
|
|
return i; |
|
} |
|
|
|
|
|
int CBaseAnimatingOverlay::AddGesture( Activity activity, float flDuration, bool autokill /*= true*/ ) |
|
{ |
|
int iLayer = AddGesture( activity, autokill ); |
|
SetLayerDuration( iLayer, flDuration ); |
|
|
|
return iLayer; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
|
|
void CBaseAnimatingOverlay::SetLayerDuration( int iLayer, float flDuration ) |
|
{ |
|
if (IsValidLayer( iLayer ) && flDuration > 0) |
|
{ |
|
m_AnimOverlay[iLayer].m_flPlaybackRate = SequenceDuration( m_AnimOverlay[iLayer].m_nSequence ) / flDuration; |
|
} |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
|
|
float CBaseAnimatingOverlay::GetLayerDuration( int iLayer ) |
|
{ |
|
if (IsValidLayer( iLayer )) |
|
{ |
|
if (m_AnimOverlay[iLayer].m_flPlaybackRate != 0.0f) |
|
{ |
|
return (1.0 - m_AnimOverlay[iLayer].m_flCycle) * SequenceDuration( m_AnimOverlay[iLayer].m_nSequence ) / m_AnimOverlay[iLayer].m_flPlaybackRate; |
|
} |
|
return SequenceDuration( m_AnimOverlay[iLayer].m_nSequence ); |
|
} |
|
return 0.0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int CBaseAnimatingOverlay::AddLayeredSequence( int sequence, int iPriority ) |
|
{ |
|
int i = AllocateLayer( iPriority ); |
|
// No room? |
|
if ( IsValidLayer( i ) ) |
|
{ |
|
m_AnimOverlay[i].m_flCycle = 0; |
|
m_AnimOverlay[i].m_flPrevCycle = 0; |
|
m_AnimOverlay[i].m_flPlaybackRate = 1.0; |
|
m_AnimOverlay[i].m_nActivity = ACT_INVALID; |
|
m_AnimOverlay[i].m_nSequence = sequence; |
|
m_AnimOverlay[i].m_flWeight = 1.0f; |
|
m_AnimOverlay[i].m_flBlendIn = 0.0f; |
|
m_AnimOverlay[i].m_flBlendOut = 0.0f; |
|
m_AnimOverlay[i].m_bSequenceFinished = false; |
|
m_AnimOverlay[i].m_flLastEventCheck = 0; |
|
m_AnimOverlay[i].m_bLooping = ((GetSequenceFlags( GetModelPtr(), sequence ) & STUDIO_LOOPING) != 0); |
|
if (ai_sequence_debug.GetBool() == true && m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) |
|
{ |
|
Msg("%5.3f : adding %d (%d): %s : %5.3f (%.3f)\n", gpGlobals->curtime, i, m_AnimOverlay[ i ].m_nOrder.Get(), GetSequenceName( m_AnimOverlay[ i ].m_nSequence ), m_AnimOverlay[ i ].m_flCycle.Get(), m_AnimOverlay[ i ].m_flWeight.Get() ); |
|
} |
|
} |
|
|
|
return i; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
bool CBaseAnimatingOverlay::IsValidLayer( int iLayer ) |
|
{ |
|
return (iLayer >= 0 && iLayer < m_AnimOverlay.Count() && m_AnimOverlay[iLayer].IsActive()); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int CBaseAnimatingOverlay::AllocateLayer( int iPriority ) |
|
{ |
|
int i; |
|
|
|
// look for an open slot and for existing layers that are lower priority |
|
int iNewOrder = 0; |
|
int iOpenLayer = -1; |
|
int iNumOpen = 0; |
|
for (i = 0; i < m_AnimOverlay.Count(); i++) |
|
{ |
|
if ( m_AnimOverlay[i].IsActive() ) |
|
{ |
|
if (m_AnimOverlay[i].m_nPriority <= iPriority) |
|
{ |
|
iNewOrder = MAX( iNewOrder, m_AnimOverlay[i].m_nOrder + 1 ); |
|
} |
|
} |
|
else if (m_AnimOverlay[ i ].IsDying()) |
|
{ |
|
// skip |
|
} |
|
else if (iOpenLayer == -1) |
|
{ |
|
iOpenLayer = i; |
|
} |
|
else |
|
{ |
|
iNumOpen++; |
|
} |
|
} |
|
|
|
if (iOpenLayer == -1) |
|
{ |
|
if (m_AnimOverlay.Count() >= MAX_OVERLAYS) |
|
{ |
|
return -1; |
|
} |
|
|
|
iOpenLayer = m_AnimOverlay.AddToTail(); |
|
m_AnimOverlay[iOpenLayer].Init( this ); |
|
} |
|
|
|
// make sure there's always an empty unused layer so that history slots will be available on the client when it is used |
|
if (iNumOpen == 0) |
|
{ |
|
if (m_AnimOverlay.Count() < MAX_OVERLAYS) |
|
{ |
|
i = m_AnimOverlay.AddToTail(); |
|
m_AnimOverlay[i].Init( this ); |
|
} |
|
} |
|
|
|
for (i = 0; i < m_AnimOverlay.Count(); i++) |
|
{ |
|
if ( m_AnimOverlay[i].m_nOrder >= iNewOrder && m_AnimOverlay[i].m_nOrder < MAX_OVERLAYS) |
|
{ |
|
m_AnimOverlay[i].m_nOrder++; |
|
} |
|
} |
|
|
|
m_AnimOverlay[iOpenLayer].m_fFlags = ANIM_LAYER_ACTIVE; |
|
m_AnimOverlay[iOpenLayer].m_nOrder = iNewOrder; |
|
m_AnimOverlay[iOpenLayer].m_nPriority = iPriority; |
|
|
|
m_AnimOverlay[iOpenLayer].MarkActive(); |
|
VerifyOrder(); |
|
|
|
return iOpenLayer; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
void CBaseAnimatingOverlay::SetLayerPriority( int iLayer, int iPriority ) |
|
{ |
|
if (!IsValidLayer( iLayer )) |
|
{ |
|
return; |
|
} |
|
|
|
if (m_AnimOverlay[iLayer].m_nPriority == iPriority) |
|
{ |
|
return; |
|
} |
|
|
|
// look for an open slot and for existing layers that are lower priority |
|
int i; |
|
for (i = 0; i < m_AnimOverlay.Count(); i++) |
|
{ |
|
if ( m_AnimOverlay[i].IsActive() ) |
|
{ |
|
if (m_AnimOverlay[i].m_nOrder > m_AnimOverlay[iLayer].m_nOrder) |
|
{ |
|
m_AnimOverlay[i].m_nOrder--; |
|
} |
|
} |
|
} |
|
|
|
int iNewOrder = 0; |
|
for (i = 0; i < m_AnimOverlay.Count(); i++) |
|
{ |
|
if ( i != iLayer && m_AnimOverlay[i].IsActive() ) |
|
{ |
|
if (m_AnimOverlay[i].m_nPriority <= iPriority) |
|
{ |
|
iNewOrder = MAX( iNewOrder, m_AnimOverlay[i].m_nOrder + 1 ); |
|
} |
|
} |
|
} |
|
|
|
for (i = 0; i < m_AnimOverlay.Count(); i++) |
|
{ |
|
if ( i != iLayer && m_AnimOverlay[i].IsActive() ) |
|
{ |
|
if ( m_AnimOverlay[i].m_nOrder >= iNewOrder) |
|
{ |
|
m_AnimOverlay[i].m_nOrder++; |
|
} |
|
} |
|
} |
|
|
|
m_AnimOverlay[iLayer].m_nOrder = iNewOrder; |
|
m_AnimOverlay[iLayer].m_nPriority = iPriority; |
|
m_AnimOverlay[iLayer].MarkActive( ); |
|
|
|
VerifyOrder(); |
|
|
|
return; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : activity - |
|
//----------------------------------------------------------------------------- |
|
int CBaseAnimatingOverlay::FindGestureLayer( Activity activity ) |
|
{ |
|
for (int i = 0; i < m_AnimOverlay.Count(); i++) |
|
{ |
|
if ( !(m_AnimOverlay[i].IsActive()) ) |
|
continue; |
|
|
|
if ( m_AnimOverlay[i].IsKillMe() ) |
|
continue; |
|
|
|
if ( m_AnimOverlay[i].m_nActivity == ACT_INVALID ) |
|
continue; |
|
|
|
if ( m_AnimOverlay[i].m_nActivity == activity ) |
|
return i; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : activity - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseAnimatingOverlay::IsPlayingGesture( Activity activity ) |
|
{ |
|
return FindGestureLayer( activity ) != -1 ? true : false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : activity - |
|
//----------------------------------------------------------------------------- |
|
void CBaseAnimatingOverlay::RestartGesture( Activity activity, bool addifmissing /*=true*/, bool autokill /*=true*/ ) |
|
{ |
|
int idx = FindGestureLayer( activity ); |
|
if ( idx == -1 ) |
|
{ |
|
if ( addifmissing ) |
|
{ |
|
AddGesture( activity, autokill ); |
|
} |
|
return; |
|
} |
|
|
|
m_AnimOverlay[ idx ].m_flCycle = 0.0f; |
|
m_AnimOverlay[ idx ].m_flPrevCycle = 0.0f; |
|
m_AnimOverlay[ idx ].m_flLastEventCheck = 0.0f; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : activity - |
|
//----------------------------------------------------------------------------- |
|
void CBaseAnimatingOverlay::RemoveGesture( Activity activity ) |
|
{ |
|
int iLayer = FindGestureLayer( activity ); |
|
if ( iLayer == -1 ) |
|
return; |
|
|
|
RemoveLayer( iLayer ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseAnimatingOverlay::RemoveAllGestures( void ) |
|
{ |
|
for (int i = 0; i < m_AnimOverlay.Count(); i++) |
|
{ |
|
RemoveLayer( i ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseAnimatingOverlay::SetLayerCycle( int iLayer, float flCycle ) |
|
{ |
|
if (!IsValidLayer( iLayer )) |
|
return; |
|
|
|
if (!m_AnimOverlay[iLayer].m_bLooping) |
|
{ |
|
flCycle = clamp( flCycle, 0.0f, 1.0f ); |
|
} |
|
m_AnimOverlay[iLayer].m_flCycle = flCycle; |
|
m_AnimOverlay[iLayer].MarkActive( ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseAnimatingOverlay::SetLayerCycle( int iLayer, float flCycle, float flPrevCycle ) |
|
{ |
|
if (!IsValidLayer( iLayer )) |
|
return; |
|
|
|
if (!m_AnimOverlay[iLayer].m_bLooping) |
|
{ |
|
flCycle = clamp( flCycle, 0.0f, 1.0f ); |
|
flPrevCycle = clamp( flPrevCycle, 0.0f, 1.0f ); |
|
} |
|
m_AnimOverlay[iLayer].m_flCycle = flCycle; |
|
m_AnimOverlay[iLayer].m_flPrevCycle = flPrevCycle; |
|
m_AnimOverlay[iLayer].m_flLastEventCheck = flPrevCycle; |
|
m_AnimOverlay[iLayer].MarkActive( ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseAnimatingOverlay::SetLayerCycle( int iLayer, float flCycle, float flPrevCycle, float flLastEventCheck ) |
|
{ |
|
if (!IsValidLayer( iLayer )) |
|
return; |
|
|
|
if (!m_AnimOverlay[iLayer].m_bLooping) |
|
{ |
|
flCycle = clamp( flCycle, 0.0f, 1.0f ); |
|
flPrevCycle = clamp( flPrevCycle, 0.0f, 1.0f ); |
|
} |
|
m_AnimOverlay[iLayer].m_flCycle = flCycle; |
|
m_AnimOverlay[iLayer].m_flPrevCycle = flPrevCycle; |
|
m_AnimOverlay[iLayer].m_flLastEventCheck = flLastEventCheck; |
|
m_AnimOverlay[iLayer].MarkActive( ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
float CBaseAnimatingOverlay::GetLayerCycle( int iLayer ) |
|
{ |
|
if (!IsValidLayer( iLayer )) |
|
return 0.0; |
|
|
|
return m_AnimOverlay[iLayer].m_flCycle; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseAnimatingOverlay::SetLayerPlaybackRate( int iLayer, float flPlaybackRate ) |
|
{ |
|
if (!IsValidLayer( iLayer )) |
|
return; |
|
|
|
Assert( flPlaybackRate > -1.0 && flPlaybackRate < 40.0); |
|
|
|
m_AnimOverlay[iLayer].m_flPlaybackRate = flPlaybackRate; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseAnimatingOverlay::SetLayerWeight( int iLayer, float flWeight ) |
|
{ |
|
if (!IsValidLayer( iLayer )) |
|
return; |
|
|
|
flWeight = clamp( flWeight, 0.0f, 1.0f ); |
|
m_AnimOverlay[iLayer].m_flWeight = flWeight; |
|
m_AnimOverlay[iLayer].MarkActive( ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
float CBaseAnimatingOverlay::GetLayerWeight( int iLayer ) |
|
{ |
|
if (!IsValidLayer( iLayer )) |
|
return 0.0; |
|
|
|
return m_AnimOverlay[iLayer].m_flWeight; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseAnimatingOverlay::SetLayerBlendIn( int iLayer, float flBlendIn ) |
|
{ |
|
if (!IsValidLayer( iLayer )) |
|
return; |
|
|
|
m_AnimOverlay[iLayer].m_flBlendIn = flBlendIn; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseAnimatingOverlay::SetLayerBlendOut( int iLayer, float flBlendOut ) |
|
{ |
|
if (!IsValidLayer( iLayer )) |
|
return; |
|
|
|
m_AnimOverlay[iLayer].m_flBlendOut = flBlendOut; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseAnimatingOverlay::SetLayerAutokill( int iLayer, bool bAutokill ) |
|
{ |
|
if (!IsValidLayer( iLayer )) |
|
return; |
|
|
|
if (bAutokill) |
|
{ |
|
m_AnimOverlay[iLayer].m_fFlags |= ANIM_LAYER_AUTOKILL; |
|
} |
|
else |
|
{ |
|
m_AnimOverlay[iLayer].m_fFlags &= ~ANIM_LAYER_AUTOKILL; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseAnimatingOverlay::SetLayerLooping( int iLayer, bool bLooping ) |
|
{ |
|
if (!IsValidLayer( iLayer )) |
|
return; |
|
|
|
m_AnimOverlay[iLayer].m_bLooping = bLooping; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseAnimatingOverlay::SetLayerNoRestore( int iLayer, bool bNoRestore ) |
|
{ |
|
if (!IsValidLayer( iLayer )) |
|
return; |
|
|
|
if (bNoRestore) |
|
{ |
|
m_AnimOverlay[iLayer].m_fFlags |= ANIM_LAYER_DONTRESTORE; |
|
} |
|
else |
|
{ |
|
m_AnimOverlay[iLayer].m_fFlags &= ~ANIM_LAYER_DONTRESTORE; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
Activity CBaseAnimatingOverlay::GetLayerActivity( int iLayer ) |
|
{ |
|
if (!IsValidLayer( iLayer )) |
|
{ |
|
return ACT_INVALID; |
|
} |
|
|
|
return m_AnimOverlay[iLayer].m_nActivity; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CBaseAnimatingOverlay::GetLayerSequence( int iLayer ) |
|
{ |
|
if (!IsValidLayer( iLayer )) |
|
{ |
|
return ACT_INVALID; |
|
} |
|
|
|
return m_AnimOverlay[iLayer].m_nSequence; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseAnimatingOverlay::RemoveLayer( int iLayer, float flKillRate, float flKillDelay ) |
|
{ |
|
if (!IsValidLayer( iLayer )) |
|
return; |
|
|
|
if (flKillRate > 0) |
|
{ |
|
m_AnimOverlay[iLayer].m_flKillRate = m_AnimOverlay[iLayer].m_flWeight / flKillRate; |
|
} |
|
else |
|
{ |
|
m_AnimOverlay[iLayer].m_flKillRate = 100; |
|
} |
|
|
|
m_AnimOverlay[iLayer].m_flKillDelay = flKillDelay; |
|
|
|
m_AnimOverlay[iLayer].KillMe(); |
|
} |
|
|
|
void CBaseAnimatingOverlay::FastRemoveLayer( int iLayer ) |
|
{ |
|
if (!IsValidLayer( iLayer )) |
|
return; |
|
|
|
// shift the other layers down in order |
|
for (int j = 0; j < m_AnimOverlay.Count(); j++ ) |
|
{ |
|
if ((m_AnimOverlay[ j ].IsActive()) && m_AnimOverlay[ j ].m_nOrder > m_AnimOverlay[ iLayer ].m_nOrder) |
|
{ |
|
m_AnimOverlay[ j ].m_nOrder--; |
|
} |
|
} |
|
m_AnimOverlay[ iLayer ].Init( this ); |
|
|
|
VerifyOrder(); |
|
} |
|
|
|
CAnimationLayer *CBaseAnimatingOverlay::GetAnimOverlay( int iIndex ) |
|
{ |
|
iIndex = clamp( iIndex, 0, m_AnimOverlay.Count()-1 ); |
|
|
|
return &m_AnimOverlay[iIndex]; |
|
} |
|
|
|
|
|
void CBaseAnimatingOverlay::SetNumAnimOverlays( int num ) |
|
{ |
|
if ( m_AnimOverlay.Count() < num ) |
|
{ |
|
m_AnimOverlay.AddMultipleToTail( num - m_AnimOverlay.Count() ); |
|
} |
|
else if ( m_AnimOverlay.Count() > num ) |
|
{ |
|
m_AnimOverlay.RemoveMultiple( num, m_AnimOverlay.Count() - num ); |
|
} |
|
} |
|
|
|
bool CBaseAnimatingOverlay::HasActiveLayer( void ) |
|
{ |
|
for (int j = 0; j < m_AnimOverlay.Count(); j++ ) |
|
{ |
|
if ( m_AnimOverlay[ j ].IsActive() ) |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//-----------------------------------------------------------------------------
|
|
|