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.
1105 lines
28 KiB
1105 lines
28 KiB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//=============================================================================// |
|
#include "cbase.h" |
|
#include "studio.h" |
|
#include "activitylist.h" |
|
#include "engine/IEngineSound.h" |
|
#include "ai_activity.h" |
|
#include "animation.h" |
|
#include "bone_setup.h" |
|
#include "scriptevent.h" |
|
#include "npcevent.h" |
|
#include "eventlist.h" |
|
#include "tier0/vprof.h" |
|
|
|
#if !defined( CLIENT_DLL ) && !defined( MAKEXVCD ) |
|
#include "util.h" |
|
#include "enginecallback.h" |
|
#endif |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
#pragma warning( disable : 4244 ) |
|
#define iabs(i) (( (i) >= 0 ) ? (i) : -(i) ) |
|
|
|
int ExtractBbox( CStudioHdr *pstudiohdr, int sequence, Vector& mins, Vector& maxs ) |
|
{ |
|
if (! pstudiohdr) |
|
return 0; |
|
|
|
if (!pstudiohdr->SequencesAvailable()) |
|
return 0; |
|
|
|
mstudioseqdesc_t &seqdesc = pstudiohdr->pSeqdesc( sequence ); |
|
|
|
mins = seqdesc.bbmin; |
|
|
|
maxs = seqdesc.bbmax; |
|
|
|
return 1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// |
|
// Input : *pstudiohdr - |
|
// iSequence - |
|
// |
|
// Output : mstudioseqdesc_t |
|
//----------------------------------------------------------------------------- |
|
|
|
extern int g_nActivityListVersion; |
|
extern int g_nEventListVersion; |
|
|
|
void SetEventIndexForSequence( mstudioseqdesc_t &seqdesc ) |
|
{ |
|
if ( &seqdesc == NULL ) |
|
return; |
|
|
|
if ( seqdesc.numevents == 0 ) |
|
return; |
|
|
|
#ifndef CLIENT_DLL |
|
seqdesc.flags |= STUDIO_EVENT; |
|
#else |
|
seqdesc.flags |= STUDIO_EVENT_CLIENT; |
|
#endif |
|
|
|
for ( int index = 0; index < (int)seqdesc.numevents; index++ ) |
|
{ |
|
mstudioevent_t *pevent = (mstudioevent_for_client_server_t*)seqdesc.pEvent( index ); |
|
|
|
if ( !pevent ) |
|
continue; |
|
|
|
if ( pevent->type & AE_TYPE_NEWEVENTSYSTEM ) |
|
{ |
|
const char *pEventName = pevent->pszEventName(); |
|
|
|
int iEventIndex = EventList_IndexForName( pEventName ); |
|
|
|
if ( iEventIndex == -1 ) |
|
{ |
|
pevent->event_newsystem = EventList_RegisterPrivateEvent( pEventName ); |
|
} |
|
else |
|
{ |
|
pevent->event_newsystem = iEventIndex; |
|
pevent->type |= EventList_GetEventType( iEventIndex ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
mstudioevent_for_client_server_t *GetEventIndexForSequence( mstudioseqdesc_t &seqdesc ) |
|
{ |
|
#ifndef CLIENT_DLL |
|
if ( !(seqdesc.flags & STUDIO_EVENT) ) |
|
#else |
|
if ( !(seqdesc.flags & STUDIO_EVENT_CLIENT) ) |
|
#endif |
|
{ |
|
SetEventIndexForSequence( seqdesc ); |
|
} |
|
|
|
return (mstudioevent_for_client_server_t*)seqdesc.pEvent( 0 ); |
|
} |
|
|
|
|
|
void BuildAllAnimationEventIndexes( CStudioHdr *pstudiohdr ) |
|
{ |
|
if ( !pstudiohdr ) |
|
return; |
|
|
|
if( pstudiohdr->GetEventListVersion() != g_nEventListVersion ) |
|
{ |
|
for ( int i = 0 ; i < pstudiohdr->GetNumSeq() ; i++ ) |
|
{ |
|
SetEventIndexForSequence( pstudiohdr->pSeqdesc( i ) ); |
|
} |
|
|
|
pstudiohdr->SetEventListVersion( g_nEventListVersion ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Ensures that activity / index relationship is recalculated |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void ResetEventIndexes( CStudioHdr *pstudiohdr ) |
|
{ |
|
if (! pstudiohdr) |
|
return; |
|
|
|
pstudiohdr->SetEventListVersion( g_nEventListVersion - 1 ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
|
|
void SetActivityForSequence( CStudioHdr *pstudiohdr, int i ) |
|
{ |
|
int iActivityIndex; |
|
const char *pszActivityName; |
|
mstudioseqdesc_t &seqdesc = pstudiohdr->pSeqdesc( i ); |
|
|
|
seqdesc.flags |= STUDIO_ACTIVITY; |
|
|
|
pszActivityName = GetSequenceActivityName( pstudiohdr, i ); |
|
if ( pszActivityName[0] != '\0' ) |
|
{ |
|
iActivityIndex = ActivityList_IndexForName( pszActivityName ); |
|
|
|
if ( iActivityIndex == -1 ) |
|
{ |
|
// Allow this now. Animators can create custom activities that are referenced only on the client or by scripts, etc. |
|
//Warning( "***\nModel %s tried to reference unregistered activity: %s \n***\n", pstudiohdr->name, pszActivityName ); |
|
//Assert(0); |
|
// HACK: the client and server don't share the private activity list so registering it on the client would hose the server |
|
#ifdef CLIENT_DLL |
|
seqdesc.flags &= ~STUDIO_ACTIVITY; |
|
#else |
|
seqdesc.activity = ActivityList_RegisterPrivateActivity( pszActivityName ); |
|
#endif |
|
} |
|
else |
|
{ |
|
seqdesc.activity = iActivityIndex; |
|
} |
|
} |
|
} |
|
|
|
//========================================================= |
|
// IndexModelSequences - set activity and event indexes for all model |
|
// sequences that have them. |
|
//========================================================= |
|
|
|
void IndexModelSequences( CStudioHdr *pstudiohdr ) |
|
{ |
|
int i; |
|
|
|
if (! pstudiohdr) |
|
return; |
|
|
|
if (!pstudiohdr->SequencesAvailable()) |
|
return; |
|
|
|
for ( i = 0 ; i < pstudiohdr->GetNumSeq() ; i++ ) |
|
{ |
|
SetActivityForSequence( pstudiohdr, i ); |
|
SetEventIndexForSequence( pstudiohdr->pSeqdesc( i ) ); |
|
} |
|
|
|
pstudiohdr->SetActivityListVersion( g_nActivityListVersion ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Ensures that activity / index relationship is recalculated |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void ResetActivityIndexes( CStudioHdr *pstudiohdr ) |
|
{ |
|
if (! pstudiohdr) |
|
return; |
|
|
|
pstudiohdr->SetActivityListVersion( g_nActivityListVersion - 1 ); |
|
} |
|
|
|
void VerifySequenceIndex( CStudioHdr *pstudiohdr ) |
|
{ |
|
if ( !pstudiohdr ) |
|
{ |
|
return; |
|
} |
|
|
|
if( pstudiohdr->GetActivityListVersion( ) != g_nActivityListVersion ) |
|
{ |
|
// this model's sequences have not yet been indexed by activity |
|
IndexModelSequences( pstudiohdr ); |
|
} |
|
} |
|
|
|
#if !defined( MAKEXVCD ) |
|
bool IsInPrediction() |
|
{ |
|
return CBaseEntity::GetPredictionPlayer() != NULL; |
|
} |
|
|
|
int SelectWeightedSequence( CStudioHdr *pstudiohdr, int activity, int curSequence ) |
|
{ |
|
VPROF( "SelectWeightedSequence" ); |
|
#ifdef CLIENT_DLL |
|
VPROF_INCREMENT_COUNTER( "Client SelectWeightedSequence", 1 ); |
|
#else // ifdef GAME_DLL |
|
VPROF_INCREMENT_COUNTER( "Server SelectWeightedSequence", 1 ); |
|
#endif |
|
|
|
if (! pstudiohdr) |
|
return 0; |
|
|
|
if (!pstudiohdr->SequencesAvailable()) |
|
return 0; |
|
|
|
VerifySequenceIndex( pstudiohdr ); |
|
|
|
int numSeq = pstudiohdr->GetNumSeq(); |
|
if ( numSeq == 1 ) |
|
{ |
|
return ( GetSequenceActivity( pstudiohdr, 0, NULL ) == activity ) ? 0 : ACTIVITY_NOT_AVAILABLE; |
|
} |
|
|
|
return pstudiohdr->SelectWeightedSequence( activity, curSequence ); |
|
} |
|
|
|
|
|
// Pick a sequence for the given activity. If the current sequence is appropriate for the |
|
// current activity, and its stored weight is negative (whatever that means), always select |
|
// it. Otherwise perform a weighted selection -- imagine a large roulette wheel, with each |
|
// sequence having a number of spaces corresponding to its weight. |
|
int CStudioHdr::CActivityToSequenceMapping::SelectWeightedSequence( CStudioHdr *pstudiohdr, int activity, int curSequence ) |
|
{ |
|
// is the current sequence appropriate? |
|
if (curSequence >= 0) |
|
{ |
|
mstudioseqdesc_t &seqdesc = pstudiohdr->pSeqdesc( curSequence ); |
|
|
|
if (seqdesc.activity == activity && seqdesc.actweight < 0) |
|
return curSequence; |
|
} |
|
|
|
if ( !pstudiohdr->SequencesAvailable() ) |
|
{ |
|
return ACTIVITY_NOT_AVAILABLE; |
|
} |
|
|
|
if ( pstudiohdr->GetNumSeq() == 1 ) |
|
{ |
|
AssertMsg( 0, "Expected single sequence case to be handled in ::SelectWeightedSequence()" ); |
|
return ACTIVITY_NOT_AVAILABLE; |
|
} |
|
|
|
if (!ValidateAgainst(pstudiohdr)) |
|
{ |
|
AssertMsg1(false, "CStudioHdr %s has changed its vmodel pointer without reinitializing its activity mapping! Now performing emergency reinitialization.", pstudiohdr->pszName()); |
|
ExecuteOnce(DebuggerBreakIfDebugging()); |
|
Reinitialize(pstudiohdr); |
|
} |
|
|
|
// a null m_pSequenceTuples just means that this studio header has no activities. |
|
if (!m_pSequenceTuples) |
|
return ACTIVITY_NOT_AVAILABLE; |
|
|
|
// get the data for the given activity |
|
HashValueType dummy( activity, 0, 0, 0 ); |
|
UtlHashHandle_t handle = m_ActToSeqHash.Find(dummy); |
|
if (!m_ActToSeqHash.IsValidHandle(handle)) |
|
{ |
|
return ACTIVITY_NOT_AVAILABLE; |
|
} |
|
const HashValueType * __restrict actData = &m_ActToSeqHash[handle]; |
|
|
|
AssertMsg2( actData->totalWeight > 0, "Activity %s has total weight of %d!", |
|
activity, actData->totalWeight ); |
|
int weighttotal = actData->totalWeight; |
|
|
|
// failsafe if the weight is 0: assume the artist screwed up and that the first sequence |
|
// for this activity should be returned. |
|
int randomValue = 0; |
|
if ( actData->totalWeight <= 0 ) |
|
{ |
|
Warning( "Activity %s has %d sequences with a total weight of %d!", ActivityList_NameForIndex(activity), actData->count, actData->totalWeight ); |
|
return (m_pSequenceTuples + actData->startingIdx)->seqnum; |
|
} |
|
else if ( actData->totalWeight == 1 ) |
|
{ |
|
randomValue = 0; |
|
} |
|
else |
|
{ |
|
// generate a random number from 0 to the total weight |
|
if ( IsInPrediction() ) |
|
{ |
|
randomValue = SharedRandomInt( "SelectWeightedSequence", 0, weighttotal - 1 ); |
|
} |
|
else |
|
{ |
|
randomValue = RandomInt( 0, weighttotal - 1 ); |
|
} |
|
} |
|
|
|
|
|
// chug through the entries in the list (they are sequential therefore cache-coherent) |
|
// until we run out of random juice |
|
SequenceTuple * __restrict sequenceInfo = m_pSequenceTuples + actData->startingIdx; |
|
|
|
const SequenceTuple *const stopHere = sequenceInfo + actData->count; // this is a backup |
|
// in case the weights are somehow miscalculated -- we don't read or write through |
|
// it (because it aliases the restricted pointer above); it's only here for |
|
// the comparison. |
|
|
|
while (randomValue >= sequenceInfo->weight && sequenceInfo < stopHere) |
|
{ |
|
randomValue -= sequenceInfo->weight; |
|
++sequenceInfo; |
|
} |
|
|
|
return sequenceInfo->seqnum; |
|
|
|
} |
|
|
|
int CStudioHdr::CActivityToSequenceMapping::SelectWeightedSequenceFromModifiers( CStudioHdr *pstudiohdr, int activity, CUtlSymbol *pActivityModifiers, int iModifierCount ) |
|
{ |
|
if ( !pstudiohdr->SequencesAvailable() ) |
|
{ |
|
return ACTIVITY_NOT_AVAILABLE; |
|
} |
|
|
|
VerifySequenceIndex( pstudiohdr ); |
|
|
|
if ( pstudiohdr->GetNumSeq() == 1 ) |
|
{ |
|
return ( ::GetSequenceActivity( pstudiohdr, 0, NULL ) == activity ) ? 0 : ACTIVITY_NOT_AVAILABLE; |
|
} |
|
|
|
if (!ValidateAgainst(pstudiohdr)) |
|
{ |
|
AssertMsg1(false, "CStudioHdr %s has changed its vmodel pointer without reinitializing its activity mapping! Now performing emergency reinitialization.", pstudiohdr->pszName()); |
|
ExecuteOnce(DebuggerBreakIfDebugging()); |
|
Reinitialize(pstudiohdr); |
|
} |
|
|
|
// a null m_pSequenceTuples just means that this studio header has no activities. |
|
if (!m_pSequenceTuples) |
|
return ACTIVITY_NOT_AVAILABLE; |
|
|
|
// get the data for the given activity |
|
HashValueType dummy( activity, 0, 0, 0 ); |
|
UtlHashHandle_t handle = m_ActToSeqHash.Find(dummy); |
|
if (!m_ActToSeqHash.IsValidHandle(handle)) |
|
{ |
|
return ACTIVITY_NOT_AVAILABLE; |
|
} |
|
const HashValueType * __restrict actData = &m_ActToSeqHash[handle]; |
|
|
|
// go through each sequence and give it a score |
|
int top_score = -1; |
|
CUtlVector<int> topScoring( actData->count, actData->count ); |
|
for ( int i = 0; i < actData->count; i++ ) |
|
{ |
|
SequenceTuple * __restrict sequenceInfo = m_pSequenceTuples + actData->startingIdx + i; |
|
int score = 0; |
|
// count matching activity modifiers |
|
for ( int m = 0; m < iModifierCount; m++ ) |
|
{ |
|
int num_modifiers = sequenceInfo->iNumActivityModifiers; |
|
for ( int k = 0; k < num_modifiers; k++ ) |
|
{ |
|
if ( sequenceInfo->pActivityModifiers[ k ] == pActivityModifiers[ m ] ) |
|
{ |
|
score++; |
|
break; |
|
} |
|
} |
|
} |
|
if ( score > top_score ) |
|
{ |
|
topScoring.RemoveAll(); |
|
topScoring.AddToTail( sequenceInfo->seqnum ); |
|
top_score = score; |
|
} |
|
} |
|
|
|
// randomly pick between the highest scoring sequences ( NOTE: this method of selecting a sequence ignores activity weights ) |
|
if ( IsInPrediction() ) |
|
{ |
|
return topScoring[ SharedRandomInt( "SelectWeightedSequence", 0, topScoring.Count() - 1 ) ]; |
|
} |
|
|
|
return topScoring[ RandomInt( 0, topScoring.Count() - 1 ) ]; |
|
} |
|
|
|
|
|
#endif |
|
|
|
int SelectHeaviestSequence( CStudioHdr *pstudiohdr, int activity ) |
|
{ |
|
if ( !pstudiohdr ) |
|
return 0; |
|
|
|
VerifySequenceIndex( pstudiohdr ); |
|
|
|
int maxweight = 0; |
|
int seq = ACTIVITY_NOT_AVAILABLE; |
|
int weight = 0; |
|
for (int i = 0; i < pstudiohdr->GetNumSeq(); i++) |
|
{ |
|
int curActivity = GetSequenceActivity( pstudiohdr, i, &weight ); |
|
if (curActivity == activity) |
|
{ |
|
if ( iabs(weight) > maxweight ) |
|
{ |
|
maxweight = iabs(weight); |
|
seq = i; |
|
} |
|
} |
|
} |
|
|
|
return seq; |
|
} |
|
|
|
void GetEyePosition ( CStudioHdr *pstudiohdr, Vector &vecEyePosition ) |
|
{ |
|
if ( !pstudiohdr ) |
|
{ |
|
Warning( "GetEyePosition() Can't get pstudiohdr ptr!\n" ); |
|
return; |
|
} |
|
|
|
vecEyePosition = pstudiohdr->eyeposition(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Looks up an activity by name. |
|
// Input : label - Name of the activity to look up, ie "ACT_IDLE" |
|
// Output : Activity index or ACT_INVALID if not found. |
|
//----------------------------------------------------------------------------- |
|
int LookupActivity( CStudioHdr *pstudiohdr, const char *label ) |
|
{ |
|
VPROF( "LookupActivity" ); |
|
|
|
if ( !pstudiohdr ) |
|
{ |
|
return 0; |
|
} |
|
|
|
for ( int i = 0; i < pstudiohdr->GetNumSeq(); i++ ) |
|
{ |
|
mstudioseqdesc_t &seqdesc = pstudiohdr->pSeqdesc( i ); |
|
if ( stricmp( seqdesc.pszActivityName(), label ) == 0 ) |
|
{ |
|
return seqdesc.activity; |
|
} |
|
} |
|
|
|
return ACT_INVALID; |
|
} |
|
|
|
#if !defined( MAKEXVCD ) |
|
//----------------------------------------------------------------------------- |
|
// Purpose: Looks up a sequence by sequence name first, then by activity name. |
|
// Input : label - The sequence name or activity name to look up. |
|
// Output : Returns the sequence index of the matching sequence, or ACT_INVALID. |
|
//----------------------------------------------------------------------------- |
|
int LookupSequence( CStudioHdr *pstudiohdr, const char *label ) |
|
{ |
|
VPROF( "LookupSequence" ); |
|
|
|
if (! pstudiohdr) |
|
return 0; |
|
|
|
if (!pstudiohdr->SequencesAvailable()) |
|
return 0; |
|
|
|
// |
|
// Look up by sequence name. |
|
// |
|
for (int i = 0; i < pstudiohdr->GetNumSeq(); i++) |
|
{ |
|
mstudioseqdesc_t &seqdesc = pstudiohdr->pSeqdesc( i ); |
|
if (stricmp( seqdesc.pszLabel(), label ) == 0) |
|
return i; |
|
} |
|
|
|
// |
|
// Not found, look up by activity name. |
|
// |
|
int nActivity = LookupActivity( pstudiohdr, label ); |
|
if (nActivity != ACT_INVALID ) |
|
{ |
|
return SelectWeightedSequence( pstudiohdr, nActivity ); |
|
} |
|
|
|
return ACT_INVALID; |
|
} |
|
|
|
void GetSequenceLinearMotion( CStudioHdr *pstudiohdr, int iSequence, const float poseParameter[], Vector *pVec ) |
|
{ |
|
if (! pstudiohdr) |
|
{ |
|
Msg( "Bad pstudiohdr in GetSequenceLinearMotion()!\n" ); |
|
return; |
|
} |
|
|
|
if (!pstudiohdr->SequencesAvailable()) |
|
return; |
|
|
|
if( iSequence < 0 || iSequence >= pstudiohdr->GetNumSeq() ) |
|
{ |
|
// Don't spam on bogus model |
|
if ( pstudiohdr->GetNumSeq() > 0 ) |
|
{ |
|
static int msgCount = 0; |
|
while ( ++msgCount < 10 ) |
|
{ |
|
Msg( "Bad sequence (%i out of %i max) in GetSequenceLinearMotion() for model '%s'!\n", iSequence, pstudiohdr->GetNumSeq(), pstudiohdr->pszName() ); |
|
} |
|
} |
|
pVec->Init(); |
|
return; |
|
} |
|
|
|
QAngle vecAngles; |
|
Studio_SeqMovement( pstudiohdr, iSequence, 0, 1.0, poseParameter, (*pVec), vecAngles ); |
|
} |
|
|
|
float GetSequenceLinearMotionAndDuration( CStudioHdr *pstudiohdr, int iSequence, const float poseParameter[], Vector *pVec ) |
|
{ |
|
pVec->Init(); |
|
if ( !pstudiohdr ) |
|
{ |
|
Msg( "Bad pstudiohdr in GetSequenceLinearMotion()!\n" ); |
|
return 0.0f; |
|
} |
|
|
|
if ( !pstudiohdr->SequencesAvailable() ) |
|
return 0.0f; |
|
|
|
if ( iSequence < 0 || iSequence >= pstudiohdr->GetNumSeq() ) |
|
{ |
|
// Don't spam on bogus model |
|
if ( pstudiohdr->GetNumSeq() > 0 ) |
|
{ |
|
static int msgCount = 0; |
|
while ( ++msgCount < 10 ) |
|
{ |
|
Msg( "Bad sequence (%i out of %i max) in GetSequenceLinearMotion() for model '%s'!\n", iSequence, pstudiohdr->GetNumSeq(), pstudiohdr->pszName() ); |
|
} |
|
} |
|
return 0.0f; |
|
} |
|
|
|
return Studio_SeqMovementAndDuration( pstudiohdr, iSequence, 0, 1.0, poseParameter, (*pVec) ); |
|
} |
|
|
|
#endif |
|
|
|
const char *GetSequenceName( CStudioHdr *pstudiohdr, int iSequence ) |
|
{ |
|
if( !pstudiohdr || iSequence < 0 || iSequence >= pstudiohdr->GetNumSeq() ) |
|
{ |
|
if ( pstudiohdr ) |
|
{ |
|
Msg( "Bad sequence in GetSequenceName() for model '%s'!\n", pstudiohdr->pszName() ); |
|
} |
|
return "Unknown"; |
|
} |
|
|
|
mstudioseqdesc_t &seqdesc = pstudiohdr->pSeqdesc( iSequence ); |
|
return seqdesc.pszLabel(); |
|
} |
|
|
|
const char *GetSequenceActivityName( CStudioHdr *pstudiohdr, int iSequence ) |
|
{ |
|
if( !pstudiohdr || iSequence < 0 || iSequence >= pstudiohdr->GetNumSeq() ) |
|
{ |
|
if ( pstudiohdr ) |
|
{ |
|
Msg( "Bad sequence in GetSequenceActivityName() for model '%s'!\n", pstudiohdr->pszName() ); |
|
} |
|
return "Unknown"; |
|
} |
|
|
|
mstudioseqdesc_t &seqdesc = pstudiohdr->pSeqdesc( iSequence ); |
|
return seqdesc.pszActivityName( ); |
|
} |
|
|
|
int GetSequenceFlags( CStudioHdr *pstudiohdr, int sequence ) |
|
{ |
|
if ( !pstudiohdr || |
|
!pstudiohdr->SequencesAvailable() || |
|
sequence < 0 || |
|
sequence >= pstudiohdr->GetNumSeq() ) |
|
{ |
|
return 0; |
|
} |
|
|
|
mstudioseqdesc_t &seqdesc = pstudiohdr->pSeqdesc( sequence ); |
|
|
|
return seqdesc.flags; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pstudiohdr - |
|
// sequence - |
|
// type - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool HasAnimationEventOfType( CStudioHdr *pstudiohdr, int sequence, int type ) |
|
{ |
|
if ( !pstudiohdr || sequence >= pstudiohdr->GetNumSeq() ) |
|
return false; |
|
|
|
mstudioseqdesc_t &seqdesc = pstudiohdr->pSeqdesc( sequence ); |
|
if ( !&seqdesc ) |
|
return false; |
|
|
|
mstudioevent_t *pevent = GetEventIndexForSequence( seqdesc ); |
|
if ( !pevent ) |
|
return false; |
|
|
|
if (seqdesc.numevents == 0 ) |
|
return false; |
|
|
|
int index; |
|
for ( index = 0; index < (int)seqdesc.numevents; index++ ) |
|
{ |
|
if ( pevent[ index ].Event() == type ) |
|
{ |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
int GetAnimationEvent( CStudioHdr *pstudiohdr, int sequence, animevent_t *pNPCEvent, float flStart, float flEnd, int index ) |
|
{ |
|
if ( !pstudiohdr || sequence >= pstudiohdr->GetNumSeq() || !pNPCEvent ) |
|
return 0; |
|
|
|
mstudioseqdesc_t &seqdesc = pstudiohdr->pSeqdesc( sequence ); |
|
if (seqdesc.numevents == 0 || index >= (int)seqdesc.numevents ) |
|
return 0; |
|
|
|
// Msg( "flStart %f flEnd %f (%d) %s\n", flStart, flEnd, seqdesc.numevents, seqdesc.label ); |
|
mstudioevent_t *pevent = GetEventIndexForSequence( seqdesc ); |
|
for (; index < (int)seqdesc.numevents; index++) |
|
{ |
|
// Don't send client-side events to the server AI |
|
if ( pevent[index].type & AE_TYPE_NEWEVENTSYSTEM ) |
|
{ |
|
if ( !(pevent[index].type & AE_TYPE_SERVER) ) |
|
continue; |
|
} |
|
else if ( pevent[index].Event_OldSystem() >= EVENT_CLIENT ) //Adrian - Support the old event system |
|
continue; |
|
|
|
bool bOverlapEvent = false; |
|
|
|
if (pevent[index].cycle >= flStart && pevent[index].cycle < flEnd) |
|
{ |
|
bOverlapEvent = true; |
|
} |
|
// FIXME: doesn't work with animations being played in reverse |
|
else if ((seqdesc.flags & STUDIO_LOOPING) && flEnd < flStart) |
|
{ |
|
if (pevent[index].cycle >= flStart || pevent[index].cycle < flEnd) |
|
{ |
|
bOverlapEvent = true; |
|
} |
|
} |
|
|
|
if (bOverlapEvent) |
|
{ |
|
pNPCEvent->pSource = NULL; |
|
pNPCEvent->cycle = pevent[index].cycle; |
|
#if !defined( MAKEXVCD ) |
|
pNPCEvent->eventtime = gpGlobals->curtime; |
|
#else |
|
pNPCEvent->eventtime = 0.0f; |
|
#endif |
|
pNPCEvent->type = pevent[index].type; |
|
pNPCEvent->Event( pevent[index].Event() ); |
|
pNPCEvent->options = pevent[index].pszOptions(); |
|
return index + 1; |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
|
|
|
|
int FindTransitionSequence( CStudioHdr *pstudiohdr, int iCurrentSequence, int iGoalSequence, int *piDir ) |
|
{ |
|
if ( !pstudiohdr ) |
|
return iGoalSequence; |
|
|
|
if ( !pstudiohdr->SequencesAvailable() ) |
|
return iGoalSequence; |
|
|
|
if ( ( iCurrentSequence < 0 ) || ( iCurrentSequence >= pstudiohdr->GetNumSeq() ) ) |
|
return iGoalSequence; |
|
|
|
if ( ( iGoalSequence < 0 ) || ( iGoalSequence >= pstudiohdr->GetNumSeq() ) ) |
|
{ |
|
// asking for a bogus sequence. Punt. |
|
Assert( 0 ); |
|
return iGoalSequence; |
|
} |
|
|
|
|
|
// bail if we're going to or from a node 0 |
|
if (pstudiohdr->EntryNode( iCurrentSequence ) == 0 || pstudiohdr->EntryNode( iGoalSequence ) == 0) |
|
{ |
|
*piDir = 1; |
|
return iGoalSequence; |
|
} |
|
|
|
int iEndNode; |
|
|
|
// Msg( "from %d to %d: ", pEndNode->iEndNode, pGoalNode->iStartNode ); |
|
|
|
// check to see if we should be going forward or backward through the graph |
|
if (*piDir > 0) |
|
{ |
|
iEndNode = pstudiohdr->ExitNode( iCurrentSequence ); |
|
} |
|
else |
|
{ |
|
iEndNode = pstudiohdr->EntryNode( iCurrentSequence ); |
|
} |
|
|
|
// if both sequences are on the same node, just go there |
|
if (iEndNode == pstudiohdr->EntryNode( iGoalSequence )) |
|
{ |
|
*piDir = 1; |
|
return iGoalSequence; |
|
} |
|
|
|
int iInternNode = pstudiohdr->GetTransition( iEndNode, pstudiohdr->EntryNode( iGoalSequence ) ); |
|
|
|
// if there is no transitionial node, just go to the goal sequence |
|
if (iInternNode == 0) |
|
return iGoalSequence; |
|
|
|
int i; |
|
|
|
// look for someone going from the entry node to next node it should hit |
|
// this may be the goal sequences node or an intermediate node |
|
for (i = 0; i < pstudiohdr->GetNumSeq(); i++) |
|
{ |
|
mstudioseqdesc_t &seqdesc = pstudiohdr->pSeqdesc(i ); |
|
if (pstudiohdr->EntryNode( i ) == iEndNode && pstudiohdr->ExitNode( i ) == iInternNode) |
|
{ |
|
*piDir = 1; |
|
return i; |
|
} |
|
if (seqdesc.nodeflags) |
|
{ |
|
if (pstudiohdr->ExitNode( i ) == iEndNode && pstudiohdr->EntryNode( i ) == iInternNode) |
|
{ |
|
*piDir = -1; |
|
return i; |
|
} |
|
} |
|
} |
|
|
|
// this means that two parts of the node graph are not connected. |
|
DevMsg( 2, "error in transition graph: %s to %s\n", pstudiohdr->pszNodeName( iEndNode ), pstudiohdr->pszNodeName( pstudiohdr->EntryNode( iGoalSequence ) )); |
|
// Go ahead and jump to the goal sequence |
|
return iGoalSequence; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
bool GotoSequence( CStudioHdr *pstudiohdr, int iCurrentSequence, float flCurrentCycle, float flCurrentRate, int iGoalSequence, int &nNextSequence, float &flNextCycle, int &iNextDir ) |
|
{ |
|
if ( !pstudiohdr ) |
|
return false; |
|
|
|
if ( !pstudiohdr->SequencesAvailable() ) |
|
return false; |
|
|
|
if ( ( iCurrentSequence < 0 ) || ( iCurrentSequence >= pstudiohdr->GetNumSeq() ) ) |
|
return false; |
|
|
|
if ( ( iGoalSequence < 0 ) || ( iGoalSequence >= pstudiohdr->GetNumSeq() ) ) |
|
{ |
|
// asking for a bogus sequence. Punt. |
|
Assert( 0 ); |
|
return false; |
|
} |
|
|
|
// bail if we're going to or from a node 0 |
|
if (pstudiohdr->EntryNode( iCurrentSequence ) == 0 || pstudiohdr->EntryNode( iGoalSequence ) == 0) |
|
{ |
|
iNextDir = 1; |
|
flNextCycle = 0.0; |
|
nNextSequence = iGoalSequence; |
|
return true; |
|
} |
|
|
|
int iEndNode = pstudiohdr->ExitNode( iCurrentSequence ); |
|
// Msg( "from %d to %d: ", pEndNode->iEndNode, pGoalNode->iStartNode ); |
|
|
|
// if we're in a transition sequence |
|
if (pstudiohdr->EntryNode( iCurrentSequence ) != pstudiohdr->ExitNode( iCurrentSequence )) |
|
{ |
|
// are we done with it? |
|
if (flCurrentRate > 0.0 && flCurrentCycle >= 0.999) |
|
{ |
|
iEndNode = pstudiohdr->ExitNode( iCurrentSequence ); |
|
} |
|
else if (flCurrentRate < 0.0 && flCurrentCycle <= 0.001) |
|
{ |
|
iEndNode = pstudiohdr->EntryNode( iCurrentSequence ); |
|
} |
|
else |
|
{ |
|
// nope, exit |
|
return false; |
|
} |
|
} |
|
|
|
// if both sequences are on the same node, just go there |
|
if (iEndNode == pstudiohdr->EntryNode( iGoalSequence )) |
|
{ |
|
iNextDir = 1; |
|
flNextCycle = 0.0; |
|
nNextSequence = iGoalSequence; |
|
return true; |
|
} |
|
|
|
int iInternNode = pstudiohdr->GetTransition( iEndNode, pstudiohdr->EntryNode( iGoalSequence ) ); |
|
|
|
// if there is no transitionial node, just go to the goal sequence |
|
if (iInternNode == 0) |
|
{ |
|
iNextDir = 1; |
|
flNextCycle = 0.0; |
|
nNextSequence = iGoalSequence; |
|
return true; |
|
} |
|
|
|
int i; |
|
|
|
// look for someone going from the entry node to next node it should hit |
|
// this may be the goal sequences node or an intermediate node |
|
for (i = 0; i < pstudiohdr->GetNumSeq(); i++) |
|
{ |
|
mstudioseqdesc_t &seqdesc = pstudiohdr->pSeqdesc(i ); |
|
if (pstudiohdr->EntryNode( i ) == iEndNode && pstudiohdr->ExitNode( i ) == iInternNode) |
|
{ |
|
iNextDir = 1; |
|
flNextCycle = 0.0; |
|
nNextSequence = i; |
|
return true; |
|
} |
|
if (seqdesc.nodeflags) |
|
{ |
|
if (pstudiohdr->ExitNode( i ) == iEndNode && pstudiohdr->EntryNode( i ) == iInternNode) |
|
{ |
|
iNextDir = -1; |
|
flNextCycle = 0.999; |
|
nNextSequence = i; |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
// this means that two parts of the node graph are not connected. |
|
DevMsg( 2, "error in transition graph: %s to %s\n", pstudiohdr->pszNodeName( iEndNode ), pstudiohdr->pszNodeName( pstudiohdr->EntryNode( iGoalSequence ) )); |
|
return false; |
|
} |
|
|
|
void SetBodygroup( CStudioHdr *pstudiohdr, int& body, int iGroup, int iValue ) |
|
{ |
|
if (! pstudiohdr) |
|
return; |
|
|
|
if (iGroup >= pstudiohdr->numbodyparts()) |
|
return; |
|
|
|
mstudiobodyparts_t *pbodypart = pstudiohdr->pBodypart( iGroup ); |
|
|
|
if (iValue >= pbodypart->nummodels) |
|
return; |
|
|
|
int iCurrent = (body / pbodypart->base) % pbodypart->nummodels; |
|
|
|
body = (body - (iCurrent * pbodypart->base) + (iValue * pbodypart->base)); |
|
} |
|
|
|
|
|
int GetBodygroup( CStudioHdr *pstudiohdr, int body, int iGroup ) |
|
{ |
|
if (! pstudiohdr) |
|
return 0; |
|
|
|
if (iGroup >= pstudiohdr->numbodyparts()) |
|
return 0; |
|
|
|
mstudiobodyparts_t *pbodypart = pstudiohdr->pBodypart( iGroup ); |
|
|
|
if (pbodypart->nummodels <= 1) |
|
return 0; |
|
|
|
int iCurrent = (body / pbodypart->base) % pbodypart->nummodels; |
|
|
|
return iCurrent; |
|
} |
|
|
|
const char *GetBodygroupName( CStudioHdr *pstudiohdr, int iGroup ) |
|
{ |
|
if ( !pstudiohdr) |
|
return ""; |
|
|
|
if (iGroup >= pstudiohdr->numbodyparts()) |
|
return ""; |
|
|
|
mstudiobodyparts_t *pbodypart = pstudiohdr->pBodypart( iGroup ); |
|
return pbodypart->pszName(); |
|
} |
|
|
|
const char *GetBodygroupPartName( CStudioHdr *pstudiohdr, int iGroup, int iPart ) |
|
{ |
|
if ( !pstudiohdr) |
|
return ""; |
|
|
|
if (iGroup >= pstudiohdr->numbodyparts()) |
|
return ""; |
|
|
|
mstudiobodyparts_t *pbodypart = pstudiohdr->pBodypart( iGroup ); |
|
if ( iPart < 0 && iPart >= pbodypart->nummodels ) |
|
return ""; |
|
|
|
return pbodypart->pModel( iPart )->name; |
|
} |
|
|
|
int FindBodygroupByName( CStudioHdr *pstudiohdr, const char *name ) |
|
{ |
|
if ( !pstudiohdr ) |
|
return -1; |
|
|
|
int group; |
|
for ( group = 0; group < pstudiohdr->numbodyparts(); group++ ) |
|
{ |
|
mstudiobodyparts_t *pbodypart = pstudiohdr->pBodypart( group ); |
|
if ( !Q_strcasecmp( name, pbodypart->pszName() ) ) |
|
{ |
|
return group; |
|
} |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
int GetBodygroupCount( CStudioHdr *pstudiohdr, int iGroup ) |
|
{ |
|
if ( !pstudiohdr ) |
|
return 0; |
|
|
|
if (iGroup >= pstudiohdr->numbodyparts()) |
|
return 0; |
|
|
|
mstudiobodyparts_t *pbodypart = pstudiohdr->pBodypart( iGroup ); |
|
return pbodypart->nummodels; |
|
} |
|
|
|
int GetNumBodyGroups( CStudioHdr *pstudiohdr ) |
|
{ |
|
if ( !pstudiohdr ) |
|
return 0; |
|
|
|
return pstudiohdr->numbodyparts(); |
|
} |
|
|
|
int GetSequenceActivity( CStudioHdr *pstudiohdr, int sequence, int *pweight ) |
|
{ |
|
if (!pstudiohdr || !pstudiohdr->SequencesAvailable() ) |
|
{ |
|
if (pweight) |
|
*pweight = 0; |
|
return 0; |
|
} |
|
|
|
Assert(sequence >= 0 && sequence < pstudiohdr->GetNumSeq()); |
|
mstudioseqdesc_t &seqdesc = pstudiohdr->pSeqdesc( sequence ); |
|
|
|
if (!(seqdesc.flags & STUDIO_ACTIVITY)) |
|
{ |
|
SetActivityForSequence( pstudiohdr, sequence ); |
|
} |
|
if (pweight) |
|
*pweight = seqdesc.actweight; |
|
return seqdesc.activity; |
|
} |
|
|
|
|
|
void GetAttachmentLocalSpace( CStudioHdr *pstudiohdr, int attachIndex, matrix3x4_t &pLocalToWorld ) |
|
{ |
|
if ( attachIndex >= 0 ) |
|
{ |
|
const mstudioattachment_t &pAttachment = pstudiohdr->pAttachment(attachIndex); |
|
MatrixCopy( pAttachment.local, pLocalToWorld ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pstudiohdr - |
|
// *name - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int FindHitboxSetByName( CStudioHdr *pstudiohdr, const char *name ) |
|
{ |
|
if ( !pstudiohdr ) |
|
return -1; |
|
|
|
for ( int i = 0; i < pstudiohdr->numhitboxsets(); i++ ) |
|
{ |
|
mstudiohitboxset_t *set = pstudiohdr->pHitboxSet( i ); |
|
if ( !set ) |
|
continue; |
|
|
|
if ( !stricmp( set->pszName(), name ) ) |
|
return i; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pstudiohdr - |
|
// setnumber - |
|
// Output : char const |
|
//----------------------------------------------------------------------------- |
|
const char *GetHitboxSetName( CStudioHdr *pstudiohdr, int setnumber ) |
|
{ |
|
if ( !pstudiohdr ) |
|
return ""; |
|
|
|
mstudiohitboxset_t *set = pstudiohdr->pHitboxSet( setnumber ); |
|
if ( !set ) |
|
return ""; |
|
|
|
return set->pszName(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pstudiohdr - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int GetHitboxSetCount( CStudioHdr *pstudiohdr ) |
|
{ |
|
if ( !pstudiohdr ) |
|
return 0; |
|
|
|
return pstudiohdr->numhitboxsets(); |
|
}
|
|
|