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.
2796 lines
78 KiB
2796 lines
78 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "animation.h" |
|
#include "baseflex.h" |
|
#include "filesystem.h" |
|
#include "studio.h" |
|
#include "choreoevent.h" |
|
#include "choreoscene.h" |
|
#include "choreoactor.h" |
|
#include "vstdlib/random.h" |
|
#include "engine/IEngineSound.h" |
|
#include "tier1/strtools.h" |
|
#include "KeyValues.h" |
|
#include "ai_basenpc.h" |
|
#include "ai_navigator.h" |
|
#include "ai_moveprobe.h" |
|
#include "sceneentity.h" |
|
#include "ai_baseactor.h" |
|
#include "datacache/imdlcache.h" |
|
#include "tier1/byteswap.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
static ConVar scene_showlook( "scene_showlook", "0", FCVAR_ARCHIVE, "When playing back, show the directions of look events." ); |
|
static ConVar scene_showmoveto( "scene_showmoveto", "0", FCVAR_ARCHIVE, "When moving, show the end location." ); |
|
static ConVar scene_showunlock( "scene_showunlock", "0", FCVAR_ARCHIVE, "Show when a vcd is playing but normal AI is running." ); |
|
|
|
// static ConVar scene_checktagposition( "scene_checktagposition", "0", FCVAR_ARCHIVE, "When playing back a choreographed scene, check the current position of the tags relative to where they were authored." ); |
|
|
|
// Fake layer # to force HandleProcessSceneEvent to actually allocate the layer during npc think time instead of in between. |
|
#define REQUEST_DEFERRED_LAYER_ALLOCATION -2 |
|
|
|
extern bool g_bClientFlex; |
|
|
|
// --------------------------------------------------------------------- |
|
// |
|
// CBaseFlex -- physically simulated brush rectangular solid |
|
// |
|
// --------------------------------------------------------------------- |
|
|
|
void* SendProxy_FlexWeights( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID ) |
|
{ |
|
// Don't any flexweights to client unless scene_clientflex.GetBool() is false |
|
if ( !g_bClientFlex ) |
|
return (void*)pVarData; |
|
else |
|
return NULL; |
|
} |
|
|
|
REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_FlexWeights ); |
|
|
|
// SendTable stuff. |
|
IMPLEMENT_SERVERCLASS_ST(CBaseFlex, DT_BaseFlex) |
|
// Note we can't totally disabled flexweights transmission since some things like blink and eye tracking are still done by the server |
|
SendPropArray3 (SENDINFO_ARRAY3(m_flexWeight), SendPropFloat(SENDINFO_ARRAY(m_flexWeight), 12, SPROP_ROUNDDOWN, 0.0f, 1.0f ) /*, SendProxy_FlexWeights*/ ), |
|
SendPropInt (SENDINFO(m_blinktoggle), 1, SPROP_UNSIGNED ), |
|
SendPropVector (SENDINFO(m_viewtarget), -1, SPROP_COORD), |
|
#ifdef HL2_DLL |
|
SendPropFloat ( SENDINFO_VECTORELEM(m_vecViewOffset, 0), 0, SPROP_NOSCALE ), |
|
SendPropFloat ( SENDINFO_VECTORELEM(m_vecViewOffset, 1), 0, SPROP_NOSCALE ), |
|
SendPropFloat ( SENDINFO_VECTORELEM(m_vecViewOffset, 2), 0, SPROP_NOSCALE ), |
|
|
|
SendPropVector ( SENDINFO(m_vecLean), -1, SPROP_COORD ), |
|
SendPropVector ( SENDINFO(m_vecShift), -1, SPROP_COORD ), |
|
#endif |
|
|
|
END_SEND_TABLE() |
|
|
|
|
|
BEGIN_DATADESC( CBaseFlex ) |
|
|
|
// m_blinktoggle |
|
DEFINE_ARRAY( m_flexWeight, FIELD_FLOAT, MAXSTUDIOFLEXCTRL ), |
|
DEFINE_FIELD( m_viewtarget, FIELD_POSITION_VECTOR ), |
|
// m_SceneEvents |
|
// m_FileList |
|
DEFINE_FIELD( m_flAllowResponsesEndTime, FIELD_TIME ), |
|
// m_ActiveChoreoScenes |
|
// DEFINE_FIELD( m_LocalToGlobal, CUtlRBTree < FS_LocalToGlobal_t , unsigned short > ), |
|
// m_bUpdateLayerPriorities |
|
DEFINE_FIELD( m_flLastFlexAnimationTime, FIELD_TIME ), |
|
|
|
#ifdef HL2_DLL |
|
//DEFINE_FIELD( m_vecPrevOrigin, FIELD_POSITION_VECTOR ), |
|
//DEFINE_FIELD( m_vecPrevVelocity, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_vecLean, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_vecShift, FIELD_VECTOR ), |
|
#endif |
|
|
|
END_DATADESC() |
|
|
|
|
|
|
|
LINK_ENTITY_TO_CLASS( funCBaseFlex, CBaseFlex ); // meaningless independant class!! |
|
|
|
CBaseFlex::CBaseFlex( void ) : |
|
m_LocalToGlobal( 0, 0, FlexSettingLessFunc ) |
|
{ |
|
#ifdef _DEBUG |
|
// default constructor sets the viewtarget to NAN |
|
m_viewtarget.Init(); |
|
#endif |
|
m_bUpdateLayerPriorities = true; |
|
m_flLastFlexAnimationTime = 0.0; |
|
} |
|
|
|
CBaseFlex::~CBaseFlex( void ) |
|
{ |
|
m_LocalToGlobal.RemoveAll(); |
|
Assert( m_SceneEvents.Count() == 0 ); |
|
} |
|
|
|
void CBaseFlex::SetModel( const char *szModelName ) |
|
{ |
|
MDLCACHE_CRITICAL_SECTION(); |
|
|
|
BaseClass::SetModel( szModelName ); |
|
|
|
for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++) |
|
{ |
|
SetFlexWeight( i, 0.0f ); |
|
} |
|
} |
|
|
|
|
|
void CBaseFlex::SetViewtarget( const Vector &viewtarget ) |
|
{ |
|
m_viewtarget = viewtarget; // bah |
|
} |
|
|
|
void CBaseFlex::SetFlexWeight( LocalFlexController_t index, float value ) |
|
{ |
|
if (index >= 0 && index < GetNumFlexControllers()) |
|
{ |
|
CStudioHdr *pstudiohdr = GetModelPtr( ); |
|
if (! pstudiohdr) |
|
return; |
|
|
|
mstudioflexcontroller_t *pflexcontroller = pstudiohdr->pFlexcontroller( index ); |
|
|
|
if (pflexcontroller->max != pflexcontroller->min) |
|
{ |
|
value = (value - pflexcontroller->min) / (pflexcontroller->max - pflexcontroller->min); |
|
value = clamp( value, 0.0f, 1.0f ); |
|
} |
|
|
|
m_flexWeight.Set( index, value ); |
|
} |
|
} |
|
|
|
float CBaseFlex::GetFlexWeight( LocalFlexController_t index ) |
|
{ |
|
if (index >= 0 && index < GetNumFlexControllers()) |
|
{ |
|
CStudioHdr *pstudiohdr = GetModelPtr( ); |
|
if (! pstudiohdr) |
|
return 0; |
|
|
|
mstudioflexcontroller_t *pflexcontroller = pstudiohdr->pFlexcontroller( index ); |
|
|
|
if (pflexcontroller->max != pflexcontroller->min) |
|
{ |
|
return m_flexWeight[index] * (pflexcontroller->max - pflexcontroller->min) + pflexcontroller->min; |
|
} |
|
|
|
return m_flexWeight[index]; |
|
} |
|
return 0.0; |
|
} |
|
|
|
LocalFlexController_t CBaseFlex::FindFlexController( const char *szName ) |
|
{ |
|
for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++) |
|
{ |
|
if (stricmp( GetFlexControllerName( i ), szName ) == 0) |
|
{ |
|
return i; |
|
} |
|
} |
|
|
|
// AssertMsg( 0, UTIL_VarArgs( "flexcontroller %s couldn't be mapped!!!\n", szName ) ); |
|
return LocalFlexController_t(0); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseFlex::StartChoreoScene( CChoreoScene *scene ) |
|
{ |
|
if ( m_ActiveChoreoScenes.Find( scene ) != m_ActiveChoreoScenes.InvalidIndex() ) |
|
{ |
|
return; |
|
} |
|
|
|
m_ActiveChoreoScenes.AddToTail( scene ); |
|
m_bUpdateLayerPriorities = true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseFlex::RemoveChoreoScene( CChoreoScene *scene, bool canceled ) |
|
{ |
|
// Assert( m_ActiveChoreoScenes.Find( scene ) != m_ActiveChoreoScenes.InvalidIndex() ); |
|
|
|
m_ActiveChoreoScenes.FindAndRemove( scene ); |
|
m_bUpdateLayerPriorities = true; |
|
|
|
if (canceled) |
|
{ |
|
CAI_BaseNPC *myNpc = MyNPCPointer( ); |
|
if ( myNpc ) |
|
{ |
|
myNpc->ClearSceneLock( ); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
|
|
int CBaseFlex::GetScenePriority( CChoreoScene *scene ) |
|
{ |
|
int iPriority = 0; |
|
int c = m_ActiveChoreoScenes.Count(); |
|
// count number of channels in scenes older than current |
|
for ( int i = 0; i < c; i++ ) |
|
{ |
|
CChoreoScene *pScene = m_ActiveChoreoScenes[ i ]; |
|
if ( !pScene ) |
|
{ |
|
continue; |
|
} |
|
|
|
if ( pScene == scene ) |
|
{ |
|
break; |
|
} |
|
|
|
iPriority += pScene->GetNumChannels( ); |
|
} |
|
return iPriority; |
|
} |
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Remove all active SceneEvents |
|
//----------------------------------------------------------------------------- |
|
void CBaseFlex::ClearSceneEvents( CChoreoScene *scene, bool canceled ) |
|
{ |
|
if ( !scene ) |
|
{ |
|
m_SceneEvents.RemoveAll(); |
|
return; |
|
} |
|
|
|
for ( int i = m_SceneEvents.Count() - 1; i >= 0; i-- ) |
|
{ |
|
CSceneEventInfo *info = &m_SceneEvents[ i ]; |
|
|
|
Assert( info ); |
|
Assert( info->m_pScene ); |
|
Assert( info->m_pEvent ); |
|
|
|
if ( info->m_pScene != scene ) |
|
continue; |
|
|
|
if ( !ClearSceneEvent( info, false, canceled )) |
|
{ |
|
// unknown expression to clear!! |
|
Assert( 0 ); |
|
} |
|
|
|
// Free this slot |
|
info->m_pEvent = NULL; |
|
info->m_pScene = NULL; |
|
info->m_bStarted = false; |
|
|
|
m_SceneEvents.Remove( i ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Stop specifics of expression |
|
//----------------------------------------------------------------------------- |
|
|
|
bool CBaseFlex::ClearSceneEvent( CSceneEventInfo *info, bool fastKill, bool canceled ) |
|
{ |
|
Assert( info ); |
|
Assert( info->m_pScene ); |
|
Assert( info->m_pEvent ); |
|
|
|
// FIXME: this code looks duplicated |
|
switch ( info->m_pEvent->GetType() ) |
|
{ |
|
case CChoreoEvent::GESTURE: |
|
case CChoreoEvent::SEQUENCE: |
|
{ |
|
if (info->m_iLayer >= 0) |
|
{ |
|
if ( fastKill ) |
|
{ |
|
FastRemoveLayer( info->m_iLayer ); |
|
} |
|
else if (info->m_pEvent->GetType() == CChoreoEvent::GESTURE) |
|
{ |
|
if (canceled) |
|
{ |
|
// remove slower if interrupted |
|
RemoveLayer( info->m_iLayer, 0.5 ); |
|
} |
|
else |
|
{ |
|
RemoveLayer( info->m_iLayer, 0.1 ); |
|
} |
|
} |
|
else |
|
{ |
|
RemoveLayer( info->m_iLayer, 0.3 ); |
|
} |
|
} |
|
} |
|
return true; |
|
|
|
case CChoreoEvent::MOVETO: |
|
{ |
|
CAI_BaseNPC *myNpc = MyNPCPointer( ); |
|
if (!myNpc) |
|
return true; |
|
|
|
// cancel moveto if it's distance based, of if the event was part of a canceled vcd |
|
if (IsMoving() && (canceled || info->m_pEvent->GetDistanceToTarget() > 0.0)) |
|
{ |
|
if (!info->m_bHasArrived) |
|
{ |
|
if (info->m_pScene) |
|
{ |
|
Scene_Printf( "%s : %8.2f: MOVETO canceled but actor %s not at goal\n", info->m_pScene->GetFilename(), info->m_pScene->GetTime(), info->m_pEvent->GetActor()->GetName() ); |
|
} |
|
} |
|
myNpc->GetNavigator()->StopMoving( false ); // Stop moving |
|
} |
|
} |
|
return true; |
|
case CChoreoEvent::FACE: |
|
case CChoreoEvent::FLEXANIMATION: |
|
case CChoreoEvent::EXPRESSION: |
|
case CChoreoEvent::LOOKAT: |
|
case CChoreoEvent::GENERIC: |
|
{ |
|
// no special rules |
|
} |
|
return true; |
|
case CChoreoEvent::SPEAK: |
|
{ |
|
// Tracker 15420: Issue stopsound if we need to cut this short... |
|
if ( canceled ) |
|
{ |
|
StopSound( info->m_pEvent->GetParameters() ); |
|
|
|
#ifdef HL2_EPISODIC |
|
// If we were holding the semaphore because of this speech, release it |
|
CAI_BaseActor *pBaseActor = dynamic_cast<CAI_BaseActor*>(this); |
|
if ( pBaseActor ) |
|
{ |
|
pBaseActor->GetExpresser()->ForceNotSpeaking(); |
|
} |
|
#endif |
|
} |
|
} |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Add string indexed scene/expression/duration to list of active SceneEvents |
|
// Input : scenefile - |
|
// expression - |
|
// duration - |
|
//----------------------------------------------------------------------------- |
|
void CBaseFlex::AddSceneEvent( CChoreoScene *scene, CChoreoEvent *event, CBaseEntity *pTarget ) |
|
{ |
|
if ( !scene || !event ) |
|
{ |
|
Msg( "CBaseFlex::AddSceneEvent: scene or event was NULL!!!\n" ); |
|
return; |
|
} |
|
|
|
CChoreoActor *actor = event->GetActor(); |
|
if ( !actor ) |
|
{ |
|
Msg( "CBaseFlex::AddSceneEvent: event->GetActor() was NULL!!!\n" ); |
|
return; |
|
} |
|
|
|
|
|
CSceneEventInfo info; |
|
|
|
memset( (void *)&info, 0, sizeof( info ) ); |
|
|
|
info.m_pEvent = event; |
|
info.m_pScene = scene; |
|
info.m_hTarget = pTarget; |
|
info.m_bStarted = false; |
|
|
|
if (StartSceneEvent( &info, scene, event, actor, pTarget )) |
|
{ |
|
m_SceneEvents.AddToTail( info ); |
|
} |
|
else |
|
{ |
|
Scene_Printf( "CBaseFlex::AddSceneEvent: event failed\n" ); |
|
// Assert( 0 ); // expression failed to start |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Starting various expression types |
|
//----------------------------------------------------------------------------- |
|
|
|
bool CBaseFlex::RequestStartSequenceSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget ) |
|
{ |
|
info->m_nSequence = LookupSequence( event->GetParameters() ); |
|
|
|
// make sure sequence exists |
|
if (info->m_nSequence < 0) |
|
{ |
|
Warning( "CSceneEntity %s :\"%s\" unable to find sequence \"%s\"\n", STRING(GetEntityName()), actor->GetName(), event->GetParameters() ); |
|
return false; |
|
} |
|
|
|
// This is a bit of a hack, but we need to defer the actual allocation until Process which will sync the layer allocation |
|
// to the NPCs think/m_flAnimTime instead of some arbitrary tick |
|
info->m_iLayer = REQUEST_DEFERRED_LAYER_ALLOCATION; |
|
info->m_pActor = actor; |
|
return true; |
|
} |
|
|
|
bool CBaseFlex::RequestStartGestureSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget ) |
|
{ |
|
info->m_nSequence = LookupSequence( event->GetParameters() ); |
|
|
|
// make sure sequence exists |
|
if (info->m_nSequence < 0) |
|
{ |
|
Warning( "CSceneEntity %s :\"%s\" unable to find gesture \"%s\"\n", STRING(GetEntityName()), actor->GetName(), event->GetParameters() ); |
|
return false; |
|
} |
|
|
|
// This is a bit of a hack, but we need to defer the actual allocation until Process which will sync the layer allocation |
|
// to the NPCs think/m_flAnimTime instead of some arbitrary tick |
|
info->m_iLayer = REQUEST_DEFERRED_LAYER_ALLOCATION; |
|
info->m_pActor = actor; |
|
return true; |
|
} |
|
|
|
bool CBaseFlex::HandleStartSequenceSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor ) |
|
{ |
|
Assert( info->m_iLayer == REQUEST_DEFERRED_LAYER_ALLOCATION ); |
|
|
|
info->m_nSequence = LookupSequence( event->GetParameters() ); |
|
info->m_iLayer = -1; |
|
|
|
if (info->m_nSequence < 0) |
|
{ |
|
Warning( "CSceneEntity %s :\"%s\" unable to find sequence \"%s\"\n", STRING(GetEntityName()), actor->GetName(), event->GetParameters() ); |
|
return false; |
|
} |
|
|
|
if (!EnterSceneSequence( scene, event )) |
|
{ |
|
if (!event->GetPlayOverScript()) |
|
{ |
|
// this has failed to start |
|
Warning( "CSceneEntity %s :\"%s\" failed to start sequence \"%s\"\n", STRING(GetEntityName()), actor->GetName(), event->GetParameters() ); |
|
return false; |
|
} |
|
// Start anyways, just use normal no-movement, must be in IDLE rules |
|
} |
|
|
|
info->m_iPriority = actor->FindChannelIndex( event->GetChannel() ); |
|
info->m_iLayer = AddLayeredSequence( info->m_nSequence, info->m_iPriority + GetScenePriority( scene ) ); |
|
SetLayerNoRestore( info->m_iLayer, true ); |
|
SetLayerWeight( info->m_iLayer, 0.0 ); |
|
|
|
bool looping = ((GetSequenceFlags( GetModelPtr(), info->m_nSequence ) & STUDIO_LOOPING) != 0); |
|
if (!looping) |
|
{ |
|
// figure out the animtime when this was frame 0 |
|
float dt = scene->GetTime() - event->GetStartTime(); |
|
float seq_duration = SequenceDuration( info->m_nSequence ); |
|
float flCycle = dt / seq_duration; |
|
flCycle = flCycle - (int)flCycle; // loop |
|
SetLayerCycle( info->m_iLayer, flCycle, flCycle ); |
|
|
|
SetLayerPlaybackRate( info->m_iLayer, 0.0 ); |
|
} |
|
else |
|
{ |
|
SetLayerPlaybackRate( info->m_iLayer, 1.0 ); |
|
} |
|
|
|
if (IsMoving()) |
|
{ |
|
info->m_flWeight = 0.0; |
|
} |
|
else |
|
{ |
|
info->m_flWeight = 1.0; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool CBaseFlex::HandleStartGestureSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor ) |
|
{ |
|
Assert( info->m_iLayer == REQUEST_DEFERRED_LAYER_ALLOCATION ); |
|
|
|
info->m_nSequence = LookupSequence( event->GetParameters() ); |
|
info->m_iLayer = -1; |
|
|
|
if (info->m_nSequence < 0) |
|
{ |
|
Warning( "CSceneEntity %s :\"%s\" unable to find gesture \"%s\"\n", STRING(GetEntityName()), actor->GetName(), event->GetParameters() ); |
|
return false; |
|
} |
|
|
|
// FIXME: this seems like way too much code |
|
info->m_bIsGesture = false; |
|
KeyValues *seqKeyValues = GetSequenceKeyValues( info->m_nSequence ); |
|
if (seqKeyValues) |
|
{ |
|
// Do we have a build point section? |
|
KeyValues *pkvAllFaceposer = seqKeyValues->FindKey("faceposer"); |
|
if ( pkvAllFaceposer ) |
|
{ |
|
KeyValues *pkvType = pkvAllFaceposer->FindKey("type"); |
|
|
|
if (pkvType) |
|
{ |
|
info->m_bIsGesture = (stricmp( pkvType->GetString(), "gesture" ) == 0) ? true : false; |
|
} |
|
} |
|
|
|
// FIXME: fixup tags that should be set as "linear", should be done in faceposer |
|
char szStartLoop[CEventAbsoluteTag::MAX_EVENTTAG_LENGTH] = { "loop" }; |
|
char szEndLoop[CEventAbsoluteTag::MAX_EVENTTAG_LENGTH] = { "end" }; |
|
|
|
// check in the tag indexes |
|
KeyValues *pkvFaceposer; |
|
for ( pkvFaceposer = pkvAllFaceposer->GetFirstSubKey(); pkvFaceposer; pkvFaceposer = pkvFaceposer->GetNextKey() ) |
|
{ |
|
if (!stricmp( pkvFaceposer->GetName(), "startloop" )) |
|
{ |
|
V_strcpy_safe( szStartLoop, pkvFaceposer->GetString() ); |
|
} |
|
else if (!stricmp( pkvFaceposer->GetName(), "endloop" )) |
|
{ |
|
V_strcpy_safe( szEndLoop, pkvFaceposer->GetString() ); |
|
} |
|
} |
|
|
|
CEventAbsoluteTag *ptag; |
|
ptag = event->FindAbsoluteTag( CChoreoEvent::ORIGINAL, szStartLoop ); |
|
if (ptag) |
|
{ |
|
ptag->SetLinear( true ); |
|
} |
|
ptag = event->FindAbsoluteTag( CChoreoEvent::PLAYBACK, szStartLoop ); |
|
if (ptag) |
|
{ |
|
ptag->SetLinear( true ); |
|
} |
|
ptag = event->FindAbsoluteTag( CChoreoEvent::ORIGINAL, szEndLoop ); |
|
if (ptag) |
|
{ |
|
ptag->SetLinear( true ); |
|
} |
|
ptag = event->FindAbsoluteTag( CChoreoEvent::PLAYBACK, szEndLoop ); |
|
if (ptag) |
|
{ |
|
ptag->SetLinear( true ); |
|
} |
|
|
|
if ( pkvAllFaceposer ) |
|
{ |
|
CStudioHdr *pstudiohdr = GetModelPtr(); |
|
|
|
mstudioseqdesc_t &seqdesc = pstudiohdr->pSeqdesc( info->m_nSequence ); |
|
mstudioanimdesc_t &animdesc = pstudiohdr->pAnimdesc( pstudiohdr->iRelativeAnim( info->m_nSequence, seqdesc.anim(0,0) ) ); |
|
|
|
// check in the tag indexes |
|
KeyValues *pkvFaceposer; |
|
for ( pkvFaceposer = pkvAllFaceposer->GetFirstSubKey(); pkvFaceposer; pkvFaceposer = pkvFaceposer->GetNextKey() ) |
|
{ |
|
if (!stricmp( pkvFaceposer->GetName(), "tags" )) |
|
{ |
|
KeyValues *pkvTags; |
|
for ( pkvTags = pkvFaceposer->GetFirstSubKey(); pkvTags; pkvTags = pkvTags->GetNextKey() ) |
|
{ |
|
int maxFrame = animdesc.numframes - 2; // FIXME: this is off by one! |
|
|
|
if ( maxFrame > 0) |
|
{ |
|
float percentage = (float)pkvTags->GetInt() / maxFrame; |
|
|
|
CEventAbsoluteTag *ptag = event->FindAbsoluteTag( CChoreoEvent::ORIGINAL, pkvTags->GetName() ); |
|
if (ptag) |
|
{ |
|
if (fabs(ptag->GetPercentage() - percentage) > 0.05) |
|
{ |
|
DevWarning("%s repositioned tag: %s : %.3f -> %.3f (%s:%s:%s)\n", scene->GetFilename(), pkvTags->GetName(), ptag->GetPercentage(), percentage, scene->GetFilename(), actor->GetName(), event->GetParameters() ); |
|
// reposition tag |
|
ptag->SetPercentage( percentage ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (!event->VerifyTagOrder()) |
|
{ |
|
DevWarning("out of order tags : %s : (%s:%s:%s)\n", scene->GetFilename(), actor->GetName(), event->GetName(), event->GetParameters() ); |
|
} |
|
} |
|
|
|
seqKeyValues->deleteThis(); |
|
} |
|
|
|
// initialize posture suppression |
|
// FIXME: move priority of base animation so that layers can be inserted before |
|
// FIXME: query stopping, post idle layer to figure out correct weight |
|
// GetIdleLayerWeight()? |
|
if (!info->m_bIsGesture && IsMoving()) |
|
{ |
|
info->m_flWeight = 0.0; |
|
} |
|
else |
|
{ |
|
info->m_flWeight = 1.0; |
|
} |
|
|
|
// this happens before StudioFrameAdvance() |
|
info->m_iPriority = actor->FindChannelIndex( event->GetChannel() ); |
|
info->m_iLayer = AddLayeredSequence( info->m_nSequence, info->m_iPriority + GetScenePriority( scene ) ); |
|
SetLayerNoRestore( info->m_iLayer, true ); |
|
SetLayerDuration( info->m_iLayer, event->GetDuration() ); |
|
SetLayerWeight( info->m_iLayer, 0.0 ); |
|
|
|
bool looping = ((GetSequenceFlags( GetModelPtr(), info->m_nSequence ) & STUDIO_LOOPING) != 0); |
|
if ( looping ) |
|
{ |
|
DevMsg( 1, "vcd error, gesture %s of model %s is marked as STUDIO_LOOPING!\n", |
|
event->GetParameters(), STRING(GetModelName()) ); |
|
} |
|
|
|
SetLayerLooping( info->m_iLayer, false ); // force to not loop |
|
|
|
float duration = event->GetDuration( ); |
|
|
|
// figure out the animtime when this was frame 0 |
|
float flEventCycle = (scene->GetTime() - event->GetStartTime()) / duration; |
|
float flCycle = event->GetOriginalPercentageFromPlaybackPercentage( flEventCycle ); |
|
SetLayerCycle( info->m_iLayer, flCycle, 0.0 ); |
|
SetLayerPlaybackRate( info->m_iLayer, 0.0 ); |
|
|
|
return true; |
|
} |
|
|
|
bool CBaseFlex::StartFacingSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget ) |
|
{ |
|
if ( pTarget ) |
|
{ |
|
// Don't allow FACE commands while sitting in the vehicle |
|
CAI_BaseNPC *myNpc = MyNPCPointer(); |
|
if ( myNpc && myNpc->IsInAVehicle() ) |
|
return false; |
|
|
|
info->m_bIsMoving = false; |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
|
|
bool CBaseFlex::StartMoveToSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget ) |
|
{ |
|
if (pTarget) |
|
{ |
|
info->m_bIsMoving = false; |
|
info->m_bHasArrived = false; |
|
CAI_BaseNPC *myNpc = MyNPCPointer( ); |
|
if (!myNpc) |
|
{ |
|
return false; |
|
} |
|
|
|
EnterSceneSequence( scene, event, true ); |
|
|
|
// If they're already moving, stop them |
|
// |
|
// Don't stop them during restore because that will set a stopping path very |
|
// nearby, causing us to signal arrival prematurely in CheckSceneEventCompletion. |
|
// BEWARE: the behavior of this bug depended on the order in which the entities were restored!! |
|
if ( myNpc->IsMoving() && !scene->IsRestoring() ) |
|
{ |
|
myNpc->GetNavigator()->StopMoving( false ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFlex::StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget ) |
|
{ |
|
switch ( event->GetType() ) |
|
{ |
|
case CChoreoEvent::SEQUENCE: |
|
return RequestStartSequenceSceneEvent( info, scene, event, actor, pTarget ); |
|
|
|
case CChoreoEvent::GESTURE: |
|
return RequestStartGestureSceneEvent( info, scene, event, actor, pTarget ); |
|
|
|
case CChoreoEvent::FACE: |
|
return StartFacingSceneEvent( info, scene, event, actor, pTarget ); |
|
|
|
// FIXME: move this to an CBaseActor |
|
case CChoreoEvent::MOVETO: |
|
return StartMoveToSceneEvent( info, scene, event, actor, pTarget ); |
|
|
|
case CChoreoEvent::LOOKAT: |
|
info->m_hTarget = pTarget; |
|
return true; |
|
|
|
case CChoreoEvent::FLEXANIMATION: |
|
info->InitWeight( this ); |
|
return true; |
|
|
|
case CChoreoEvent::SPEAK: |
|
return true; |
|
|
|
case CChoreoEvent::EXPRESSION: // These are handled client-side |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Remove expression |
|
// Input : scenefile - |
|
// expression - |
|
//----------------------------------------------------------------------------- |
|
void CBaseFlex::RemoveSceneEvent( CChoreoScene *scene, CChoreoEvent *event, bool fastKill ) |
|
{ |
|
Assert( event ); |
|
|
|
for ( int i = 0 ; i < m_SceneEvents.Count(); i++ ) |
|
{ |
|
CSceneEventInfo *info = &m_SceneEvents[ i ]; |
|
|
|
Assert( info ); |
|
Assert( info->m_pEvent ); |
|
|
|
if ( info->m_pScene != scene ) |
|
continue; |
|
|
|
if ( info->m_pEvent != event) |
|
continue; |
|
|
|
if (ClearSceneEvent( info, fastKill, false )) |
|
{ |
|
// Free this slot |
|
info->m_pEvent = NULL; |
|
info->m_pScene = NULL; |
|
info->m_bStarted = false; |
|
|
|
m_SceneEvents.Remove( i ); |
|
return; |
|
} |
|
} |
|
|
|
// many events refuse to start due to bogus parameters |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Checks to see if the event should be considered "completed" |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFlex::CheckSceneEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) |
|
{ |
|
for ( int i = 0 ; i < m_SceneEvents.Count(); i++ ) |
|
{ |
|
CSceneEventInfo *info = &m_SceneEvents[ i ]; |
|
|
|
Assert( info ); |
|
Assert( info->m_pEvent ); |
|
|
|
if ( info->m_pScene != scene ) |
|
continue; |
|
|
|
if ( info->m_pEvent != event) |
|
continue; |
|
|
|
return CheckSceneEventCompletion( info, currenttime, scene, event ); |
|
} |
|
return true; |
|
} |
|
|
|
|
|
|
|
bool CBaseFlex::CheckSceneEventCompletion( CSceneEventInfo *info, float currenttime, CChoreoScene *scene, CChoreoEvent *event ) |
|
{ |
|
switch ( event->GetType() ) |
|
{ |
|
case CChoreoEvent::MOVETO: |
|
{ |
|
CAI_BaseNPC *npc = MyNPCPointer( ); |
|
|
|
if (npc) |
|
{ |
|
// check movement, check arrival |
|
if (npc->GetNavigator()->IsGoalActive()) |
|
{ |
|
const Task_t *pCurTask = npc->GetTask(); |
|
if ( pCurTask && (pCurTask->iTask == TASK_PLAY_SCENE || pCurTask->iTask == TASK_WAIT_FOR_MOVEMENT ) ) |
|
{ |
|
float preload = event->GetEndTime() - currenttime; |
|
if (preload < 0) |
|
{ |
|
//Msg("%.1f: no preload\n", currenttime ); |
|
return false; |
|
} |
|
float t = npc->GetTimeToNavGoal(); |
|
|
|
// Msg("%.1f: preload (%s:%.1f) %.1f %.1f\n", currenttime, event->GetName(), event->GetEndTime(), preload, t ); |
|
|
|
// FIXME: t is zero if no path can be built! |
|
|
|
if (t > 0.0f && t <= preload) |
|
{ |
|
return true; |
|
} |
|
return false; |
|
} |
|
} |
|
else if (info->m_bHasArrived) |
|
{ |
|
return true; |
|
} |
|
else if (info->m_bStarted && !npc->IsCurSchedule( SCHED_SCENE_GENERIC )) |
|
{ |
|
// FIXME: There's still a hole in the logic is the save happens immediately after the SS steals the npc but before their AI has run again |
|
Warning( "%s : %8.2f: waiting for actor %s to complete MOVETO but actor not in SCHED_SCENE_GENERIC\n", scene->GetFilename(), scene->GetTime(), event->GetActor()->GetName() ); |
|
// no longer in a scene :P |
|
return true; |
|
} |
|
// still trying |
|
return false; |
|
} |
|
} |
|
break; |
|
default: |
|
break; |
|
} |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Default implementation |
|
//----------------------------------------------------------------------------- |
|
void CBaseFlex::ProcessSceneEvents( void ) |
|
{ |
|
VPROF( "CBaseFlex::ProcessSceneEvents" ); |
|
// slowly decay to netural expression |
|
for ( LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++) |
|
{ |
|
SetFlexWeight( i, GetFlexWeight( i ) * 0.95 ); |
|
} |
|
|
|
bool bHasForegroundEvents = false; |
|
// Iterate SceneEvents and look for active slots |
|
for ( int i = 0; i < m_SceneEvents.Count(); i++ ) |
|
{ |
|
CSceneEventInfo *info = &m_SceneEvents[ i ]; |
|
Assert( info ); |
|
|
|
// FIXME: Need a safe handle to m_pEvent in case of memory deletion? |
|
CChoreoEvent *event = info->m_pEvent; |
|
Assert( event ); |
|
|
|
CChoreoScene *scene = info->m_pScene; |
|
Assert( scene ); |
|
|
|
if ( scene && !scene->IsBackground() ) |
|
{ |
|
bHasForegroundEvents = true; |
|
} |
|
|
|
if (ProcessSceneEvent( info, scene, event )) |
|
{ |
|
info->m_bStarted = true; |
|
} |
|
} |
|
|
|
if ( bHasForegroundEvents && scene_showunlock.GetBool()) |
|
{ |
|
CAI_BaseNPC *myNpc = MyNPCPointer( ); |
|
if ( myNpc && !(myNpc->GetState() == NPC_STATE_SCRIPT || myNpc->IsCurSchedule( SCHED_SCENE_GENERIC )) ) |
|
{ |
|
Vector p0 = myNpc->GetHullMins(); |
|
Vector p1 = myNpc->GetHullMaxs(); |
|
p0.z = p1.z + 2; |
|
p1.z = p1.z + 2; |
|
NDebugOverlay::Box( myNpc->GetAbsOrigin(), p0, p1, 255, 0, 0, 0, 0.12 ); |
|
} |
|
} |
|
|
|
|
|
// any needed layer priorites have now been reset |
|
m_bUpdateLayerPriorities = false; |
|
} |
|
|
|
class CFlexSceneFileManager : CAutoGameSystem |
|
{ |
|
public: |
|
|
|
CFlexSceneFileManager( char const *name ) : CAutoGameSystem( name ) |
|
{ |
|
} |
|
|
|
virtual bool Init() |
|
{ |
|
// Trakcer 16692: Preload these at startup to avoid hitch first time we try to load them during actual gameplay |
|
FindSceneFile( NULL, "phonemes", true ); |
|
FindSceneFile( NULL, "phonemes_weak", true ); |
|
FindSceneFile( NULL, "phonemes_strong", true ); |
|
#if defined( HL2_DLL ) |
|
FindSceneFile( NULL, "random", true ); |
|
FindSceneFile( NULL, "randomAlert", true ); |
|
#endif |
|
return true; |
|
} |
|
|
|
// Tracker 14992: We used to load 18K of .vfes for every CBaseFlex who lipsynced, but now we only load those files once globally. |
|
// Note, we could wipe these between levels, but they don't ever load more than the weak/normal/strong phoneme classes that I can tell |
|
// so I'll just leave them loaded forever for now |
|
virtual void Shutdown() |
|
{ |
|
DeleteSceneFiles(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Sets up translations |
|
// Input : *instance - |
|
// *pSettinghdr - |
|
// Output : void |
|
//----------------------------------------------------------------------------- |
|
void EnsureTranslations( CBaseFlex *instance, const flexsettinghdr_t *pSettinghdr ) |
|
{ |
|
// The only time instance is NULL is in Init() above, where we're just loading the .vfe files off of the hard disk. |
|
if ( instance ) |
|
{ |
|
instance->EnsureTranslations( pSettinghdr ); |
|
} |
|
} |
|
|
|
const void *FindSceneFile( CBaseFlex *instance, const char *filename, bool allowBlockingIO ) |
|
{ |
|
// See if it's already loaded |
|
int i; |
|
for ( i = 0; i < m_FileList.Size(); i++ ) |
|
{ |
|
CFlexSceneFile *file = m_FileList[ i ]; |
|
if ( file && !stricmp( file->filename, filename ) ) |
|
{ |
|
// Make sure translations (local to global flex controller) are set up for this instance |
|
EnsureTranslations( instance, ( const flexsettinghdr_t * )file->buffer ); |
|
return file->buffer; |
|
} |
|
} |
|
|
|
if ( !allowBlockingIO ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
// Load file into memory |
|
void *buffer = NULL; |
|
int len = filesystem->ReadFileEx( UTIL_VarArgs( "expressions/%s.vfe", filename ), "GAME", &buffer, false, true ); |
|
|
|
if ( !len ) |
|
return NULL; |
|
|
|
// Create scene entry |
|
CFlexSceneFile *pfile = new CFlexSceneFile; |
|
// Remember filename |
|
Q_strncpy( pfile->filename, filename, sizeof( pfile->filename ) ); |
|
// Remember data pointer |
|
pfile->buffer = buffer; |
|
// Add to list |
|
m_FileList.AddToTail( pfile ); |
|
|
|
// Swap the entire file |
|
if ( IsX360() ) |
|
{ |
|
CByteswap swap; |
|
swap.ActivateByteSwapping( true ); |
|
byte *pData = (byte*)buffer; |
|
flexsettinghdr_t *pHdr = (flexsettinghdr_t*)pData; |
|
swap.SwapFieldsToTargetEndian( pHdr ); |
|
|
|
// Flex Settings |
|
flexsetting_t *pFlexSetting = (flexsetting_t*)((byte*)pHdr + pHdr->flexsettingindex); |
|
for ( int i = 0; i < pHdr->numflexsettings; ++i, ++pFlexSetting ) |
|
{ |
|
swap.SwapFieldsToTargetEndian( pFlexSetting ); |
|
|
|
flexweight_t *pWeight = (flexweight_t*)(((byte*)pFlexSetting) + pFlexSetting->settingindex ); |
|
for ( int j = 0; j < pFlexSetting->numsettings; ++j, ++pWeight ) |
|
{ |
|
swap.SwapFieldsToTargetEndian( pWeight ); |
|
} |
|
} |
|
|
|
// indexes |
|
pData = (byte*)pHdr + pHdr->indexindex; |
|
swap.SwapBufferToTargetEndian( (int*)pData, (int*)pData, pHdr->numindexes ); |
|
|
|
// keymappings |
|
pData = (byte*)pHdr + pHdr->keymappingindex; |
|
swap.SwapBufferToTargetEndian( (int*)pData, (int*)pData, pHdr->numkeys ); |
|
|
|
// keyname indices |
|
pData = (byte*)pHdr + pHdr->keynameindex; |
|
swap.SwapBufferToTargetEndian( (int*)pData, (int*)pData, pHdr->numkeys ); |
|
} |
|
|
|
// Fill in translation table |
|
EnsureTranslations( instance, ( const flexsettinghdr_t * )pfile->buffer ); |
|
|
|
// Return data |
|
return pfile->buffer; |
|
} |
|
|
|
private: |
|
|
|
void DeleteSceneFiles() |
|
{ |
|
while ( m_FileList.Size() > 0 ) |
|
{ |
|
CFlexSceneFile *file = m_FileList[ 0 ]; |
|
m_FileList.Remove( 0 ); |
|
filesystem->FreeOptimalReadBuffer( file->buffer ); |
|
delete file; |
|
} |
|
} |
|
|
|
CUtlVector< CFlexSceneFile * > m_FileList; |
|
}; |
|
|
|
// Singleton manager |
|
CFlexSceneFileManager g_FlexSceneFileManager( "CFlexSceneFileManager" ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Each CBaseFlex maintains a UtlRBTree of mappings, one for each loaded flex scene file it uses. This is used to |
|
// sort the entries in the RBTree |
|
// Input : lhs - |
|
// rhs - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFlex::FlexSettingLessFunc( const FS_LocalToGlobal_t& lhs, const FS_LocalToGlobal_t& rhs ) |
|
{ |
|
return lhs.m_Key < rhs.m_Key; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Since everyone shared a pSettinghdr now, we need to set up the localtoglobal mapping per entity, but |
|
// we just do this in memory with an array of integers (could be shorts, I suppose) |
|
// Input : *pSettinghdr - |
|
//----------------------------------------------------------------------------- |
|
void CBaseFlex::EnsureTranslations( const flexsettinghdr_t *pSettinghdr ) |
|
{ |
|
Assert( pSettinghdr ); |
|
|
|
FS_LocalToGlobal_t entry( pSettinghdr ); |
|
|
|
unsigned short idx = m_LocalToGlobal.Find( entry ); |
|
if ( idx != m_LocalToGlobal.InvalidIndex() ) |
|
return; |
|
|
|
entry.SetCount( pSettinghdr->numkeys ); |
|
|
|
for ( int i = 0; i < pSettinghdr->numkeys; ++i ) |
|
{ |
|
entry.m_Mapping[ i ] = FindFlexController( pSettinghdr->pLocalName( i ) ); |
|
} |
|
|
|
m_LocalToGlobal.Insert( entry ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Look up instance specific mapping |
|
// Input : *pSettinghdr - |
|
// key - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
LocalFlexController_t CBaseFlex::FlexControllerLocalToGlobal( const flexsettinghdr_t *pSettinghdr, int key ) |
|
{ |
|
FS_LocalToGlobal_t entry( pSettinghdr ); |
|
|
|
int idx = m_LocalToGlobal.Find( entry ); |
|
if ( idx == m_LocalToGlobal.InvalidIndex() ) |
|
{ |
|
// This should never happen!!! |
|
Assert( 0 ); |
|
Warning( "Unable to find mapping for flexcontroller %i, settings %p on %i/%s\n", key, pSettinghdr, entindex(), GetClassname() ); |
|
EnsureTranslations( pSettinghdr ); |
|
idx = m_LocalToGlobal.Find( entry ); |
|
if ( idx == m_LocalToGlobal.InvalidIndex() ) |
|
{ |
|
Error( "CBaseFlex::FlexControllerLocalToGlobal failed!\n" ); |
|
} |
|
} |
|
|
|
FS_LocalToGlobal_t& result = m_LocalToGlobal[ idx ]; |
|
// Validate lookup |
|
Assert( result.m_nCount != 0 && key < result.m_nCount ); |
|
LocalFlexController_t index = result.m_Mapping[ key ]; |
|
return index; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *filename - |
|
//----------------------------------------------------------------------------- |
|
const void *CBaseFlex::FindSceneFile( const char *filename ) |
|
{ |
|
// Ask manager to get the globally cached scene instead. |
|
return g_FlexSceneFileManager.FindSceneFile( this, filename, false ); |
|
} |
|
|
|
ConVar ai_expression_optimization( "ai_expression_optimization", "0", FCVAR_NONE, "Disable npc background expressions when you can't see them." ); |
|
ConVar ai_expression_frametime( "ai_expression_frametime", "0.05", FCVAR_NONE, "Maximum frametime to still play background expressions." ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Various methods to process facial SceneEvents: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFlex::ProcessFlexAnimationSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ) |
|
{ |
|
VPROF( "CBaseFlex::ProcessFlexAnimationSceneEvent" ); |
|
|
|
if ( event->HasEndTime() ) |
|
{ |
|
// don't bother with flex animation if the player can't see you |
|
CAI_BaseNPC *myNpc = MyNPCPointer( ); |
|
if (myNpc) |
|
{ |
|
if (!myNpc->HasCondition( COND_IN_PVS )) |
|
return true; |
|
|
|
if (ai_expression_optimization.GetBool()) |
|
{ |
|
if (scene->IsBackground()) |
|
{ |
|
// if framerate too slow, disable |
|
if (gpGlobals->frametime > ai_expression_frametime.GetFloat()) |
|
{ |
|
info->m_bHasArrived = true; |
|
info->m_flNext = gpGlobals->curtime + RandomFloat( 0.7, 1.2 ); |
|
} |
|
// only check occasionally |
|
else if (info->m_flNext <= gpGlobals->curtime) |
|
{ |
|
CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); |
|
|
|
// if not in view, disable |
|
info->m_bHasArrived = (pPlayer && !pPlayer->FInViewCone( this ) ); |
|
info->m_flNext = gpGlobals->curtime + RandomFloat( 0.7, 1.2 ); |
|
} |
|
|
|
if (info->m_bHasArrived) |
|
{ |
|
// NDebugOverlay::Box( myNpc->GetAbsOrigin(), myNpc->GetHullMins(), myNpc->GetHullMaxs(), 255, 0, 0, 0, 0.22 ); |
|
return true; |
|
} |
|
// NDebugOverlay::Box( myNpc->GetAbsOrigin(), myNpc->GetHullMins(), myNpc->GetHullMaxs(), 0, 255, 0, 0, 0.22 ); |
|
} |
|
} |
|
} |
|
|
|
AddFlexAnimation( info ); |
|
} |
|
return true; |
|
} |
|
|
|
bool CBaseFlex::ProcessFlexSettingSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ) |
|
{ |
|
// Flexanimations have to have an end time!!! |
|
if ( !event->HasEndTime() ) |
|
return true; |
|
|
|
VPROF( "CBaseFlex::ProcessFlexSettingSceneEvent" ); |
|
|
|
// Look up the actual strings |
|
const char *scenefile = event->GetParameters(); |
|
const char *name = event->GetParameters2(); |
|
|
|
// Have to find both strings |
|
if ( scenefile && name ) |
|
{ |
|
// Find the scene file |
|
const flexsettinghdr_t *pExpHdr = ( const flexsettinghdr_t * )FindSceneFile( scenefile ); |
|
if ( pExpHdr ) |
|
{ |
|
float scenetime = scene->GetTime(); |
|
|
|
float scale = event->GetIntensity( scenetime ); |
|
|
|
// Add the named expression |
|
AddFlexSetting( name, scale, pExpHdr, !info->m_bStarted ); |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool CBaseFlex::ProcessFacingSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ) |
|
{ |
|
// make sure target exists |
|
if (info->m_hTarget == NULL) |
|
return false; |
|
|
|
VPROF( "CBaseFlex::ProcessFacingSceneEvent" ); |
|
|
|
// make sure we're still able to play this command |
|
if (!EnterSceneSequence( scene, event, true )) |
|
{ |
|
return false; |
|
} |
|
|
|
if (!info->m_bStarted) |
|
{ |
|
info->m_flInitialYaw = GetLocalAngles().y; |
|
} |
|
|
|
// lock in place if aiming at self |
|
if (info->m_hTarget == this) |
|
{ |
|
return true; |
|
} |
|
|
|
CAI_BaseNPC *myNpc = MyNPCPointer( ); |
|
if (myNpc) |
|
{ |
|
if (info->m_bIsMoving != IsMoving()) |
|
{ |
|
info->m_flInitialYaw = GetLocalAngles().y; |
|
} |
|
info->m_bIsMoving = IsMoving(); |
|
|
|
// Msg("%f : %f - %f\n", scene->GetTime(), event->GetStartTime(), event->GetEndTime() ); |
|
// FIXME: why are the splines ill behaved at the end? |
|
float intensity = event->GetIntensity( scene->GetTime() ); |
|
if (info->m_bIsMoving) |
|
{ |
|
myNpc->AddFacingTarget( info->m_hTarget, intensity, 0.2 ); |
|
} |
|
else |
|
{ |
|
float goalYaw = myNpc->CalcIdealYaw( info->m_hTarget->EyePosition() ); |
|
|
|
float diff = UTIL_AngleDiff( goalYaw, info->m_flInitialYaw ); |
|
|
|
float idealYaw = UTIL_AngleMod( info->m_flInitialYaw + diff * intensity ); |
|
|
|
// Msg("yaw %.1f : %.1f (%.1f)\n", info->m_flInitialYaw, idealYaw, intensity ); |
|
|
|
myNpc->GetMotor()->SetIdealYawAndUpdate( idealYaw ); |
|
} |
|
|
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
static Activity DetermineExpressionMoveActivity( CChoreoEvent *event, CAI_BaseNPC *pNPC ) |
|
{ |
|
Activity activity = ACT_WALK; |
|
const char *sParam2 = event->GetParameters2(); |
|
if ( !sParam2 || !sParam2[0] ) |
|
return activity; |
|
|
|
// Custom distance styles are appended to param2 with a space as a separator |
|
const char *pszAct = Q_strstr( sParam2, " " ); |
|
char szActName[256]; |
|
if ( pszAct ) |
|
{ |
|
Q_strncpy( szActName, sParam2, sizeof(szActName) ); |
|
szActName[ (pszAct-sParam2) ] = '\0'; |
|
pszAct = szActName; |
|
} |
|
else |
|
{ |
|
pszAct = sParam2; |
|
} |
|
|
|
if ( !Q_stricmp( pszAct, "Walk" ) ) |
|
{ |
|
activity = ACT_WALK; |
|
} |
|
else if ( !Q_stricmp( pszAct, "Run" ) ) |
|
{ |
|
activity = ACT_RUN; |
|
} |
|
else if ( !Q_stricmp( pszAct, "CrouchWalk" ) ) |
|
{ |
|
activity = ACT_WALK_CROUCH; |
|
} |
|
else |
|
{ |
|
// Try and resolve the activity name |
|
activity = (Activity)ActivityList_IndexForName( pszAct ); |
|
if ( activity == ACT_INVALID ) |
|
{ |
|
// Assume it's a sequence name |
|
pNPC->m_iszSceneCustomMoveSeq = AllocPooledString( pszAct ); |
|
activity = ACT_SCRIPT_CUSTOM_MOVE; |
|
} |
|
} |
|
|
|
return activity; |
|
} |
|
|
|
bool CBaseFlex::ProcessMoveToSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ) |
|
{ |
|
// make sure target exists |
|
if (info->m_hTarget == NULL) |
|
return false; |
|
|
|
// FIXME: move to CBaseActor or BaseNPC |
|
CAI_BaseNPC *myNpc = MyNPCPointer( ); |
|
if (!myNpc) |
|
return false; |
|
|
|
VPROF( "CBaseFlex::ProcessMoveToSceneEvent" ); |
|
|
|
// make sure we're still able to play this command |
|
if (!EnterSceneSequence( scene, event, true )) |
|
{ |
|
return false; |
|
} |
|
|
|
// lock in place if aiming at self |
|
if (info->m_hTarget == this) |
|
{ |
|
return true; |
|
} |
|
|
|
// If we're in a vehicle, make us exit and *then* begin the run |
|
if ( myNpc->IsInAVehicle() ) |
|
{ |
|
// Make us exit and wait |
|
myNpc->ExitVehicle(); |
|
return false; |
|
} |
|
|
|
const Task_t *pCurTask = myNpc->GetTask(); |
|
if (!info->m_bIsMoving && (!IsMoving() || pCurTask->iTask == TASK_STOP_MOVING) ) |
|
{ |
|
if ( pCurTask && (pCurTask->iTask == TASK_PLAY_SCENE || pCurTask->iTask == TASK_WAIT_FOR_MOVEMENT || pCurTask->iTask == TASK_STOP_MOVING ) ) |
|
{ |
|
Activity moveActivity = DetermineExpressionMoveActivity( event, myNpc ); |
|
// AI_NavGoal_t goal( info->m_hTarget->EyePosition(), moveActivity, AIN_HULL_TOLERANCE ); |
|
myNpc->SetTarget( info->m_hTarget ); |
|
|
|
float flDistTolerance; |
|
flDistTolerance = myNpc->GetHullWidth() / 2.0; |
|
// flDistTolerance = AIN_HULL_TOLERANCE; |
|
|
|
if (event->m_bForceShortMovement) |
|
{ |
|
flDistTolerance = 0.1f; |
|
} |
|
|
|
AI_NavGoal_t goal( GOALTYPE_TARGETENT, moveActivity, flDistTolerance, AIN_UPDATE_TARGET_POS ); |
|
|
|
float flDist = (info->m_hTarget->EyePosition() - GetAbsOrigin()).Length2D(); |
|
|
|
if (flDist > MAX( MAX( flDistTolerance, 0.1 ), event->GetDistanceToTarget())) |
|
{ |
|
// Msg("flDist %.1f\n", flDist ); |
|
int result = false; |
|
|
|
if ( !myNpc->IsUnreachable( info->m_hTarget ) ) |
|
{ |
|
result = myNpc->GetNavigator()->SetGoal( goal, AIN_CLEAR_TARGET ); |
|
if ( !result ) |
|
{ |
|
myNpc->RememberUnreachable( info->m_hTarget, 1.5 ); |
|
} |
|
} |
|
|
|
if (result) |
|
{ |
|
myNpc->GetNavigator()->SetMovementActivity( moveActivity ); |
|
myNpc->GetNavigator()->SetArrivalDistance( event->GetDistanceToTarget() ); |
|
info->m_bIsMoving = true; |
|
} |
|
else |
|
{ |
|
// need route build failure case |
|
// Msg("actor %s unable to build route\n", STRING( myNpc->GetEntityName() ) ); |
|
// Assert(0); |
|
|
|
if (developer.GetInt() > 0 && scene_showmoveto.GetBool()) |
|
{ |
|
Vector vTestPoint; |
|
myNpc->GetMoveProbe()->FloorPoint( info->m_hTarget->EyePosition(), MASK_NPCSOLID, 0, -64, &vTestPoint ); |
|
NDebugOverlay::HorzArrow( GetAbsOrigin() + Vector( 0, 0, 1 ), vTestPoint + Vector( 0, 0, 1 ), 4, 255, 0, 255, 0, false, 0.12 ); |
|
NDebugOverlay::Box( vTestPoint, myNpc->GetHullMins(), myNpc->GetHullMaxs(), 255, 0, 255, 0, 0.12 ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
info->m_bHasArrived = true; |
|
} |
|
} |
|
} |
|
else if (IsMoving()) |
|
{ |
|
// float flDist = (myNpc->GetNavigator()->GetGoalPos() - GetAbsOrigin()).Length2D(); |
|
float flDist = (info->m_hTarget->EyePosition() - GetAbsOrigin()).Length2D(); |
|
|
|
if (flDist <= event->GetDistanceToTarget()) |
|
{ |
|
myNpc->GetNavigator()->StopMoving( false ); // Stop moving |
|
info->m_bHasArrived = true; |
|
} |
|
} |
|
else |
|
{ |
|
info->m_bIsMoving = false; |
|
} |
|
|
|
// show movement target |
|
if (developer.GetInt() > 0 && scene_showmoveto.GetBool() && IsMoving()) |
|
{ |
|
Vector vecStart, vTestPoint; |
|
vecStart = myNpc->GetNavigator()->GetGoalPos(); |
|
|
|
myNpc->GetMoveProbe()->FloorPoint( vecStart, MASK_NPCSOLID, 0, -64, &vTestPoint ); |
|
|
|
int r, g, b; |
|
r = b = g = 0; |
|
if ( myNpc->GetNavigator()->CanFitAtPosition( vTestPoint, MASK_NPCSOLID ) ) |
|
{ |
|
if ( myNpc->GetMoveProbe()->CheckStandPosition( vTestPoint, MASK_NPCSOLID ) ) |
|
{ |
|
if (event->IsResumeCondition()) |
|
{ |
|
g = 255; |
|
} |
|
else |
|
{ |
|
r = 255; g = 255; |
|
} |
|
} |
|
else |
|
{ |
|
b = 255; g = 255; |
|
} |
|
} |
|
else |
|
{ |
|
r = 255; |
|
} |
|
|
|
NDebugOverlay::HorzArrow( GetAbsOrigin() + Vector( 0, 0, 1 ), vTestPoint + Vector( 0, 0, 1 ), 4, r, g, b, 0, false, 0.12 ); |
|
NDebugOverlay::Box( vTestPoint, myNpc->GetHullMins(), myNpc->GetHullMaxs(), r, g, b, 0, 0.12 ); |
|
} |
|
|
|
// handled in task |
|
return true; |
|
} |
|
|
|
bool CBaseFlex::ProcessLookAtSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ) |
|
{ |
|
VPROF( "CBaseFlex::ProcessLookAtSceneEvent" ); |
|
CAI_BaseNPC *myNpc = MyNPCPointer( ); |
|
if (myNpc && info->m_hTarget != NULL) |
|
{ |
|
float intensity = event->GetIntensity( scene->GetTime() ); |
|
|
|
// clamp in-ramp to 0.3 seconds |
|
float flDuration = scene->GetTime() - event->GetStartTime(); |
|
float flMaxIntensity = flDuration < 0.3f ? SimpleSpline( flDuration / 0.3f ) : 1.0f; |
|
intensity = clamp( intensity, 0.0f, flMaxIntensity ); |
|
|
|
myNpc->AddLookTarget( info->m_hTarget, intensity, 0.1 ); |
|
if (developer.GetInt() > 0 && scene_showlook.GetBool() && info->m_hTarget) |
|
{ |
|
Vector tmp = info->m_hTarget->EyePosition() - myNpc->EyePosition(); |
|
VectorNormalize( tmp ); |
|
Vector p0 = myNpc->EyePosition(); |
|
NDebugOverlay::VertArrow( p0, p0 + tmp * (4 + 16 * intensity ), 4, 255, 255, 255, 0, true, 0.12 ); |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFlex::ProcessSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ) |
|
{ |
|
VPROF( "CBaseFlex::ProcessSceneEvent" ); |
|
switch ( event->GetType() ) |
|
{ |
|
case CChoreoEvent::FLEXANIMATION: |
|
return ProcessFlexAnimationSceneEvent( info, scene, event ); |
|
|
|
case CChoreoEvent::EXPRESSION: |
|
return ProcessFlexSettingSceneEvent( info, scene, event ); |
|
|
|
case CChoreoEvent::SEQUENCE: |
|
return ProcessSequenceSceneEvent( info, scene, event ); |
|
|
|
case CChoreoEvent::GESTURE: |
|
return ProcessGestureSceneEvent( info, scene, event ); |
|
|
|
case CChoreoEvent::FACE: |
|
return ProcessFacingSceneEvent( info, scene, event ); |
|
|
|
case CChoreoEvent::MOVETO: |
|
return ProcessMoveToSceneEvent( info, scene, event ); |
|
|
|
case CChoreoEvent::LOOKAT: |
|
return ProcessLookAtSceneEvent( info, scene, event ); |
|
|
|
case CChoreoEvent::SPEAK: |
|
return true; |
|
|
|
default: |
|
{ |
|
Msg( "unknown type %d in ProcessSceneEvent()\n", event->GetType() ); |
|
Assert( 0 ); |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFlex::IsRunningSceneMoveToEvent() |
|
{ |
|
for ( int i = m_SceneEvents.Count() - 1; i >= 0; i-- ) |
|
{ |
|
CSceneEventInfo *info = &m_SceneEvents[ i ]; |
|
CChoreoEvent *event = info->m_pEvent; |
|
if ( event && event->GetType() == CChoreoEvent::MOVETO ) |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
flexsetting_t const *CBaseFlex::FindNamedSetting( flexsettinghdr_t const *pSettinghdr, const char *expr ) |
|
{ |
|
int i; |
|
const flexsetting_t *pSetting = NULL; |
|
|
|
for ( i = 0; i < pSettinghdr->numflexsettings; i++ ) |
|
{ |
|
pSetting = pSettinghdr->pSetting( i ); |
|
if ( !pSetting ) |
|
continue; |
|
|
|
const char *name = pSetting->pszName(); |
|
|
|
if ( !stricmp( name, expr ) ) |
|
break; |
|
} |
|
|
|
if ( i>=pSettinghdr->numflexsettings ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
return pSetting; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *event - |
|
//----------------------------------------------------------------------------- |
|
void CBaseFlex::AddFlexAnimation( CSceneEventInfo *info ) |
|
{ |
|
if ( !info ) |
|
return; |
|
|
|
// don't bother with flex animation if the player can't see you |
|
CAI_BaseNPC *myNpc = MyNPCPointer( ); |
|
if (myNpc && !myNpc->HasCondition( COND_IN_PVS )) |
|
return; |
|
|
|
CChoreoEvent *event = info->m_pEvent; |
|
if ( !event ) |
|
return; |
|
|
|
CChoreoScene *scene = info->m_pScene; |
|
if ( !scene ) |
|
return; |
|
|
|
if ( !event->GetTrackLookupSet() ) |
|
{ |
|
// Create lookup data |
|
for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ ) |
|
{ |
|
CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i ); |
|
if ( !track ) |
|
continue; |
|
|
|
if ( track->IsComboType() ) |
|
{ |
|
char name[ 512 ]; |
|
Q_strncpy( name, "right_" ,sizeof(name)); |
|
Q_strncat( name, track->GetFlexControllerName(),sizeof(name), COPY_ALL_CHARACTERS ); |
|
|
|
track->SetFlexControllerIndex( FindFlexController( name ), 0, 0 ); |
|
|
|
if ( CAI_BaseActor::IsServerSideFlexController( name ) ) |
|
{ |
|
Assert( !"Should stereo controllers ever be server side only?" ); |
|
track->SetServerSide( true ); |
|
} |
|
|
|
Q_strncpy( name, "left_" ,sizeof(name)); |
|
Q_strncat( name, track->GetFlexControllerName(),sizeof(name), COPY_ALL_CHARACTERS ); |
|
|
|
track->SetFlexControllerIndex( FindFlexController( name ), 0, 1 ); |
|
|
|
if ( CAI_BaseActor::IsServerSideFlexController( name ) ) |
|
{ |
|
Assert( !"Should stereo controllers ever be server side only?" ); |
|
track->SetServerSide( true ); |
|
} |
|
} |
|
else |
|
{ |
|
track->SetFlexControllerIndex( FindFlexController( (char *)track->GetFlexControllerName() ), 0 ); |
|
|
|
// Only non-combo tracks can be server side |
|
track->SetServerSide( CAI_BaseActor::IsServerSideFlexController( track->GetFlexControllerName() ) ); |
|
} |
|
} |
|
|
|
event->SetTrackLookupSet( true ); |
|
} |
|
|
|
float scenetime = scene->GetTime(); |
|
// decay if this is a background scene and there's other flex animations playing |
|
float weight = event->GetIntensity( scenetime ) * info->UpdateWeight( this ); |
|
{ |
|
VPROF( "AddFlexAnimation_SetFlexWeight" ); |
|
|
|
// Compute intensity for each track in animation and apply |
|
// Iterate animation tracks |
|
for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ ) |
|
{ |
|
CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i ); |
|
if ( !track ) |
|
continue; |
|
|
|
// Disabled |
|
if ( !track->IsTrackActive() ) |
|
continue; |
|
|
|
// If we are doing client side flexing, skip all tracks which are not server side |
|
if ( g_bClientFlex && !track->IsServerSide() ) |
|
continue; |
|
|
|
// Map track flex controller to global name |
|
if ( track->IsComboType() ) |
|
{ |
|
for ( int side = 0; side < 2; side++ ) |
|
{ |
|
LocalFlexController_t controller = track->GetRawFlexControllerIndex( side ); |
|
|
|
// Get spline intensity for controller |
|
float flIntensity = track->GetIntensity( scenetime, side ); |
|
if ( controller >= LocalFlexController_t(0) ) |
|
{ |
|
float orig = GetFlexWeight( controller ); |
|
SetFlexWeight( controller, orig * (1 - weight) + flIntensity * weight ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
LocalFlexController_t controller = track->GetRawFlexControllerIndex( 0 ); |
|
|
|
// Get spline intensity for controller |
|
float flIntensity = track->GetIntensity( scenetime, 0 ); |
|
if ( controller >= LocalFlexController_t(0) ) |
|
{ |
|
float orig = GetFlexWeight( controller ); |
|
SetFlexWeight( controller, orig * (1 - weight) + flIntensity * weight ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
info->m_bStarted = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *expr - |
|
// scale - |
|
// *pSettinghdr - |
|
// newexpression - |
|
//----------------------------------------------------------------------------- |
|
void CBaseFlex::AddFlexSetting( const char *expr, float scale, |
|
const flexsettinghdr_t *pSettinghdr, bool newexpression ) |
|
{ |
|
int i; |
|
const flexsetting_t *pSetting = NULL; |
|
|
|
// Find the named setting in the base |
|
for ( i = 0; i < pSettinghdr->numflexsettings; i++ ) |
|
{ |
|
pSetting = pSettinghdr->pSetting( i ); |
|
if ( !pSetting ) |
|
continue; |
|
|
|
const char *name = pSetting->pszName(); |
|
|
|
if ( !stricmp( name, expr ) ) |
|
break; |
|
} |
|
|
|
if ( i>=pSettinghdr->numflexsettings ) |
|
{ |
|
return; |
|
} |
|
|
|
flexweight_t *pWeights = NULL; |
|
int truecount = pSetting->psetting( (byte *)pSettinghdr, 0, &pWeights ); |
|
if ( !pWeights ) |
|
return; |
|
|
|
for (i = 0; i < truecount; i++, pWeights++) |
|
{ |
|
// Translate to local flex controller |
|
// this is translating from the settings's local index to the models local index |
|
LocalFlexController_t index = FlexControllerLocalToGlobal( pSettinghdr, pWeights->key ); |
|
|
|
// blend scaled weighting in to total |
|
float s = clamp( scale * pWeights->influence, 0.0f, 1.0f ); |
|
float value = GetFlexWeight( index ) * (1.0f - s ) + pWeights->weight * s; |
|
SetFlexWeight( index, value ); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *actor - |
|
// *parameters - |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFlex::ProcessGestureSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ) |
|
{ |
|
if ( !info || !event || !scene ) |
|
return false; |
|
|
|
if ( info->m_iLayer == REQUEST_DEFERRED_LAYER_ALLOCATION ) |
|
{ |
|
HandleStartGestureSceneEvent( info, scene, event, info->m_pActor ); |
|
} |
|
|
|
if (info->m_iLayer >= 0) |
|
{ |
|
// this happens after StudioFrameAdvance() |
|
// FIXME; this needs to be adjusted by npc offset to scene time? Known? |
|
// FIXME: what should this do when the scene loops? |
|
float duration = event->GetDuration( ); |
|
float flEventCycle = (scene->GetTime() - event->GetStartTime()) / duration; |
|
float flCycle = event->GetOriginalPercentageFromPlaybackPercentage( flEventCycle ); |
|
|
|
SetLayerCycle( info->m_iLayer, flCycle ); |
|
|
|
float flWeight = event->GetIntensity( scene->GetTime() ); |
|
|
|
/* |
|
if (stricmp( event->GetParameters(), "m_g_arms_crossed" ) == 0) |
|
{ |
|
Msg("%.2f (%.2f) : %s : %.3f (%.3f) %.2f\n", scene->GetTime(), scene->GetTime() - event->GetStartTime(), event->GetParameters(), flCycle, flEventCycle, flWeight ); |
|
} |
|
*/ |
|
|
|
// fade out/in if npc is moving |
|
if (!info->m_bIsGesture) |
|
{ |
|
if (IsMoving()) |
|
{ |
|
info->m_flWeight = MAX( info->m_flWeight - 0.2, 0.0 ); |
|
} |
|
else |
|
{ |
|
info->m_flWeight = MIN( info->m_flWeight + 0.2, 1.0 ); |
|
} |
|
} |
|
|
|
// 3x^2-2x^3 |
|
float spline = 3 * info->m_flWeight * info->m_flWeight - 2 * info->m_flWeight * info->m_flWeight * info->m_flWeight; |
|
SetLayerWeight( info->m_iLayer, flWeight * spline ); |
|
|
|
// update layer priority |
|
if (m_bUpdateLayerPriorities) |
|
{ |
|
SetLayerPriority( info->m_iLayer, info->m_iPriority + GetScenePriority( scene ) ); |
|
} |
|
|
|
/* |
|
Msg( "%d : %.2f (%.2f) : %.3f %.3f : %.3f\n", |
|
info->m_iLayer, |
|
scene->GetTime(), |
|
(scene->GetTime() - event->GetStartTime()) / duration, |
|
flCycle, |
|
flNextCycle, |
|
rate ); |
|
*/ |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *actor - |
|
// *parameters - |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFlex::ProcessSequenceSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ) |
|
{ |
|
if ( !info || !event || !scene ) |
|
return false; |
|
|
|
bool bNewlyAllocated = false; |
|
if ( info->m_iLayer == REQUEST_DEFERRED_LAYER_ALLOCATION ) |
|
{ |
|
bool result = HandleStartSequenceSceneEvent( info, scene, event, info->m_pActor ); |
|
if (!result) |
|
return false; |
|
bNewlyAllocated = true; |
|
} |
|
|
|
if (info->m_iLayer >= 0) |
|
{ |
|
float flWeight = event->GetIntensity( scene->GetTime() ); |
|
|
|
// force layer to zero weight in newly allocated, fixed bug with inter-think spawned sequences blending in badly |
|
if (bNewlyAllocated) |
|
flWeight = 0.0; |
|
|
|
CAI_BaseNPC *myNpc = MyNPCPointer( ); |
|
|
|
// fade out/in if npc is moving |
|
|
|
bool bFadeOut = IsMoving(); |
|
if (myNpc && !(myNpc->IsCurSchedule( SCHED_SCENE_GENERIC ) || myNpc->GetActivity() == ACT_IDLE_ANGRY || myNpc->GetActivity() == ACT_IDLE) ) |
|
{ |
|
bFadeOut = true; |
|
if (info->m_flWeight == 1.0) |
|
{ |
|
Warning( "%s playing CChoreoEvent::SEQUENCE but AI has forced them to do something different\n", STRING(GetEntityName()) ); |
|
} |
|
} |
|
|
|
if (bFadeOut) |
|
{ |
|
info->m_flWeight = MAX( info->m_flWeight - 0.2, 0.0 ); |
|
} |
|
else |
|
{ |
|
info->m_flWeight = MIN( info->m_flWeight + 0.2, 1.0 ); |
|
} |
|
|
|
float spline = 3 * info->m_flWeight * info->m_flWeight - 2 * info->m_flWeight * info->m_flWeight * info->m_flWeight; |
|
SetLayerWeight( info->m_iLayer, flWeight * spline ); |
|
|
|
bool looping = ((GetSequenceFlags( GetModelPtr(), info->m_nSequence ) & STUDIO_LOOPING) != 0); |
|
if (!looping) |
|
{ |
|
float dt = scene->GetTime() - event->GetStartTime(); |
|
float seq_duration = SequenceDuration( info->m_nSequence ); |
|
float flCycle = dt / seq_duration; |
|
flCycle = clamp( flCycle, 0.f, 1.0f ); |
|
SetLayerCycle( info->m_iLayer, flCycle ); |
|
} |
|
|
|
if (myNpc) |
|
{ |
|
myNpc->AddSceneLock( 0.2 ); |
|
} |
|
|
|
// update layer priority |
|
if (m_bUpdateLayerPriorities) |
|
{ |
|
SetLayerPriority( info->m_iLayer, info->m_iPriority + GetScenePriority( scene ) ); |
|
} |
|
} |
|
|
|
// FIXME: clean up cycle index from restart |
|
return true; |
|
|
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns true if the actor is not currently in a scene OR if the actor |
|
// is in a scene (checked externally), but a PERMIT_RESPONSES event is active and |
|
// the permit time period has enough time remaining to handle the response in full. |
|
// Input : response_length - |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFlex::PermitResponse( float response_length ) |
|
{ |
|
// Nothing set, disallow it |
|
if ( m_flAllowResponsesEndTime <= 0.0f ) |
|
{ |
|
return false; |
|
} |
|
|
|
// If response ends before end of allow time, then that's okay |
|
if ( gpGlobals->curtime + response_length <= m_flAllowResponsesEndTime ) |
|
{ |
|
return true; |
|
} |
|
|
|
// Disallow responses for now |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Set response end time (0 to clear response blocking) |
|
// Input : endtime - |
|
//----------------------------------------------------------------------------- |
|
void CBaseFlex::SetPermitResponse( float endtime ) |
|
{ |
|
// Mark time after which we'll be occupied again (if in a scene) |
|
// Note a value of <= 0.0f means unset (always allow actor to speak if not in a scene, |
|
// and always disallow if in a scene) |
|
m_flAllowResponsesEndTime = endtime; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Play a one-shot scene |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
float CBaseFlex::PlayScene( const char *pszScene, float flDelay, AI_Response *response, IRecipientFilter *filter /* = NULL */ ) |
|
{ |
|
return InstancedScriptedScene( this, pszScene, NULL, flDelay, false, response, false, filter ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Generate a one-shot scene in memory with one track which is to play the named sound on the actor |
|
// Input : *soundname - |
|
// Output : float |
|
//----------------------------------------------------------------------------- |
|
float CBaseFlex::PlayAutoGeneratedSoundScene( const char *soundname ) |
|
{ |
|
return InstancedAutoGeneratedSoundScene( this, soundname ); |
|
} |
|
|
|
|
|
|
|
|
|
// FIXME: move to CBaseActor |
|
bool CBaseFlex::EnterSceneSequence( CChoreoScene *scene, CChoreoEvent *event, bool bRestart ) |
|
{ |
|
CAI_BaseNPC *myNpc = MyNPCPointer( ); |
|
|
|
if (!myNpc) |
|
{ |
|
// In multiplayer, we allow players to play scenes |
|
if ( IsPlayer() ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
// 2 seconds past current event, or 0.2 seconds past end of scene, whichever is shorter |
|
float flDuration = MIN( 2.0, MIN( event->GetEndTime() - scene->GetTime() + 2.0, scene->FindStopTime() - scene->GetTime() + 0.2 ) ); |
|
|
|
if (myNpc->IsCurSchedule( SCHED_SCENE_GENERIC )) |
|
{ |
|
myNpc->AddSceneLock( flDuration ); |
|
return true; |
|
} |
|
|
|
// for now, don't interrupt sequences that don't understand being interrupted |
|
if (myNpc->GetCurSchedule()) |
|
{ |
|
CAI_ScheduleBits testBits; |
|
myNpc->GetCurSchedule()->GetInterruptMask( &testBits ); |
|
|
|
testBits.Clear( COND_PROVOKED ); |
|
|
|
if (testBits.IsAllClear()) |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
if (myNpc->IsInterruptable()) |
|
{ |
|
if (myNpc->m_hCine) |
|
{ |
|
// Assert( !(myNpc->GetFlags() & FL_FLY ) ); |
|
myNpc->ExitScriptedSequence( ); |
|
} |
|
|
|
myNpc->OnStartScene(); |
|
myNpc->SetSchedule( SCHED_SCENE_GENERIC ); |
|
myNpc->AddSceneLock( flDuration ); |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
bool CBaseFlex::ExitSceneSequence( void ) |
|
{ |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: keep track of last valid flex animation time and returns if the current info should play theirs |
|
//----------------------------------------------------------------------------- |
|
|
|
bool CBaseFlex::IsSuppressedFlexAnimation( CSceneEventInfo *info ) |
|
{ |
|
// check for suppression if the current info is a background |
|
if (info->m_pScene && info->m_pScene->IsBackground()) |
|
{ |
|
// allow for slight jitter |
|
return m_flLastFlexAnimationTime > gpGlobals->curtime - GetAnimTimeInterval() * 1.5; |
|
} |
|
// keep track of last non-suppressable flex animation |
|
m_flLastFlexAnimationTime = gpGlobals->curtime; |
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Clear out body lean states that are invalidated with Teleport |
|
//----------------------------------------------------------------------------- |
|
|
|
void CBaseFlex::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity ) |
|
{ |
|
BaseClass::Teleport( newPosition, newAngles, newVelocity ); |
|
#ifdef HL2_DLL |
|
|
|
// clear out Body Lean |
|
m_vecPrevOrigin = vec3_origin; |
|
|
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: keep track of accel/decal and lean the body |
|
//----------------------------------------------------------------------------- |
|
|
|
void CBaseFlex::DoBodyLean( void ) |
|
{ |
|
#ifdef HL2_DLL |
|
CAI_BaseNPC *myNpc = MyNPCPointer( ); |
|
|
|
if (myNpc) |
|
{ |
|
Vector vecDelta; |
|
Vector vecPos; |
|
Vector vecOrigin = GetAbsOrigin(); |
|
|
|
if (m_vecPrevOrigin == vec3_origin) |
|
{ |
|
m_vecPrevOrigin = vecOrigin; |
|
} |
|
|
|
vecDelta = vecOrigin - m_vecPrevOrigin; |
|
vecDelta.x = clamp( vecDelta.x, -50, 50 ); |
|
vecDelta.y = clamp( vecDelta.y, -50, 50 ); |
|
vecDelta.z = clamp( vecDelta.z, -50, 50 ); |
|
|
|
float dt = gpGlobals->curtime - GetLastThink(); |
|
bool bSkip = ((GetFlags() & (FL_FLY | FL_SWIM)) != 0) || (GetMoveParent() != NULL) || (GetGroundEntity() == NULL) || (GetGroundEntity()->IsMoving()); |
|
bSkip |= myNpc->TaskRanAutomovement() || (myNpc->GetVehicleEntity() != NULL); |
|
|
|
if (!bSkip) |
|
{ |
|
if (vecDelta.LengthSqr() > m_vecPrevVelocity.LengthSqr()) |
|
{ |
|
float decay = ExponentialDecay( 0.6, 0.1, dt ); |
|
m_vecPrevVelocity = m_vecPrevVelocity * (decay) + vecDelta * (1.f - decay); |
|
} |
|
else |
|
{ |
|
float decay = ExponentialDecay( 0.4, 0.1, dt ); |
|
m_vecPrevVelocity = m_vecPrevVelocity * (decay) + vecDelta * (1.f - decay); |
|
} |
|
|
|
vecPos = m_vecPrevOrigin + m_vecPrevVelocity; |
|
|
|
float decay = ExponentialDecay( 0.5, 0.1, dt ); |
|
m_vecShift = m_vecShift * (decay) + (vecOrigin - vecPos) * (1.f - decay); // FIXME: Scale this |
|
m_vecLean = (vecOrigin - vecPos) * 1.0; // FIXME: Scale this |
|
} |
|
else |
|
{ |
|
m_vecPrevVelocity = vecDelta; |
|
float decay = ExponentialDecay( 0.5, 0.1, dt ); |
|
m_vecShift = m_vecLean * decay; |
|
m_vecLean = m_vecShift * decay; |
|
} |
|
|
|
m_vecPrevOrigin = vecOrigin; |
|
|
|
/* |
|
DevMsg( "%.2f %.2f %.2f (%.2f %.2f %.2f)\n", |
|
m_vecLean.Get().x, m_vecLean.Get().y, m_vecLean.Get().z, |
|
vecDelta.x, vecDelta.y, vecDelta.z ); |
|
*/ |
|
} |
|
#endif |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: initialize weight for background events |
|
//----------------------------------------------------------------------------- |
|
|
|
void CSceneEventInfo::InitWeight( CBaseFlex *pActor ) |
|
{ |
|
if (pActor->IsSuppressedFlexAnimation( this )) |
|
{ |
|
m_flWeight = 0.0; |
|
} |
|
else |
|
{ |
|
m_flWeight = 1.0; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: update weight for background events. Only call once per think |
|
//----------------------------------------------------------------------------- |
|
|
|
float CSceneEventInfo::UpdateWeight( CBaseFlex *pActor ) |
|
{ |
|
// decay if this is a background scene and there's other flex animations playing |
|
if (pActor->IsSuppressedFlexAnimation( this )) |
|
{ |
|
m_flWeight = MAX( m_flWeight - 0.2, 0.0 ); |
|
} |
|
else |
|
{ |
|
m_flWeight = MIN( m_flWeight + 0.1, 1.0 ); |
|
} |
|
return m_flWeight; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
|
|
class CFlexCycler : public CBaseFlex |
|
{ |
|
private: |
|
DECLARE_CLASS( CFlexCycler, CBaseFlex ); |
|
public: |
|
DECLARE_DATADESC(); |
|
|
|
CFlexCycler() { m_iszSentence = NULL_STRING; m_sentence = 0; } |
|
void GenericCyclerSpawn(char *szModel, Vector vecMin, Vector vecMax); |
|
virtual int ObjectCaps( void ) { return (BaseClass::ObjectCaps() | FCAP_IMPULSE_USE); } |
|
int OnTakeDamage( const CTakeDamageInfo &info ); |
|
void Spawn( void ); |
|
void Think( void ); |
|
|
|
virtual void ProcessSceneEvents( void ); |
|
|
|
// Don't treat as a live target |
|
virtual bool IsAlive( void ) { return FALSE; } |
|
|
|
float m_flextime; |
|
LocalFlexController_t m_flexnum; |
|
float m_flextarget[64]; |
|
float m_blinktime; |
|
float m_looktime; |
|
Vector m_lookTarget; |
|
float m_speaktime; |
|
int m_istalking; |
|
int m_phoneme; |
|
|
|
string_t m_iszSentence; |
|
int m_sentence; |
|
|
|
void SetFlexTarget( LocalFlexController_t flexnum ); |
|
LocalFlexController_t LookupFlex( const char *szTarget ); |
|
}; |
|
|
|
BEGIN_DATADESC( CFlexCycler ) |
|
|
|
DEFINE_FIELD( m_flextime, FIELD_TIME ), |
|
DEFINE_FIELD( m_flexnum, FIELD_INTEGER ), |
|
DEFINE_ARRAY( m_flextarget, FIELD_FLOAT, 64 ), |
|
DEFINE_FIELD( m_blinktime, FIELD_TIME ), |
|
DEFINE_FIELD( m_looktime, FIELD_TIME ), |
|
DEFINE_FIELD( m_lookTarget, FIELD_POSITION_VECTOR ), |
|
DEFINE_FIELD( m_speaktime, FIELD_TIME ), |
|
DEFINE_FIELD( m_istalking, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_phoneme, FIELD_INTEGER ), |
|
DEFINE_KEYFIELD( m_iszSentence, FIELD_STRING, "Sentence" ), |
|
DEFINE_FIELD( m_sentence, FIELD_INTEGER ), |
|
|
|
END_DATADESC() |
|
|
|
|
|
// |
|
// we should get rid of all the other cyclers and replace them with this. |
|
// |
|
class CGenericFlexCycler : public CFlexCycler |
|
{ |
|
public: |
|
DECLARE_CLASS( CGenericFlexCycler, CFlexCycler ); |
|
|
|
void Spawn( void ) { GenericCyclerSpawn( (char *)STRING( GetModelName() ), Vector(-16, -16, 0), Vector(16, 16, 72) ); } |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( cycler_flex, CGenericFlexCycler ); |
|
|
|
|
|
|
|
ConVar flex_expression( "flex_expression","-" ); |
|
ConVar flex_talk( "flex_talk","0" ); |
|
|
|
// Cycler member functions |
|
|
|
void CFlexCycler::GenericCyclerSpawn(char *szModel, Vector vecMin, Vector vecMax) |
|
{ |
|
if (!szModel || !*szModel) |
|
{ |
|
Warning( "cycler at %.0f %.0f %0.f missing modelname\n", GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z ); |
|
UTIL_Remove( this ); |
|
return; |
|
} |
|
|
|
PrecacheModel( szModel ); |
|
SetModel( szModel ); |
|
|
|
CFlexCycler::Spawn( ); |
|
|
|
UTIL_SetSize(this, vecMin, vecMax); |
|
|
|
Vector vecEyeOffset; |
|
GetEyePosition( GetModelPtr(), vecEyeOffset ); |
|
SetViewOffset( vecEyeOffset ); |
|
|
|
InitBoneControllers(); |
|
|
|
if (GetNumFlexControllers() < 5) |
|
Warning( "cycler_flex used on model %s without enough flexes.\n", szModel ); |
|
} |
|
|
|
void CFlexCycler::Spawn( ) |
|
{ |
|
Precache(); |
|
/* |
|
if ( m_spawnflags & FCYCLER_NOTSOLID ) |
|
{ |
|
SetSolid( SOLID_NOT ); |
|
} |
|
else |
|
{ |
|
SetSolid( SOLID_SLIDEBOX ); |
|
} |
|
*/ |
|
|
|
SetSolid( SOLID_BBOX ); |
|
AddSolidFlags( FSOLID_NOT_STANDABLE ); |
|
|
|
SetMoveType( MOVETYPE_NONE ); |
|
m_takedamage = DAMAGE_YES; |
|
m_iHealth = 80000;// no cycler should die |
|
|
|
m_flPlaybackRate = 1.0f; |
|
m_flGroundSpeed = 0; |
|
|
|
|
|
SetNextThink( gpGlobals->curtime + 1.0f ); |
|
|
|
ResetSequenceInfo( ); |
|
|
|
m_flCycle = random->RandomFloat( 0, 1.0 ); |
|
} |
|
|
|
const char *predef_flexcontroller_names[] = { |
|
"right_lid_raiser", |
|
"left_lid_raiser", |
|
"right_lid_tightener", |
|
"left_lid_tightener", |
|
"right_lid_droop", |
|
"left_lid_droop", |
|
"right_inner_raiser", |
|
"left_inner_raiser", |
|
"right_outer_raiser", |
|
"left_outer_raiser", |
|
"right_lowerer", |
|
"left_lowerer", |
|
"right_cheek_raiser", |
|
"left_cheek_raiser", |
|
"wrinkler", |
|
"right_upper_raiser", |
|
"left_upper_raiser", |
|
"right_corner_puller", |
|
"left_corner_puller", |
|
"corner_depressor", |
|
"chin_raiser", |
|
"right_puckerer", |
|
"left_puckerer", |
|
"right_funneler", |
|
"left_funneler", |
|
"tightener", |
|
"jaw_clencher", |
|
"jaw_drop", |
|
"right_mouth_drop", |
|
"left_mouth_drop", |
|
NULL }; |
|
|
|
float predef_flexcontroller_values[7][30] = { |
|
/* 0 */ { 0.700,0.560,0.650,0.650,0.650,0.585,0.000,0.000,0.400,0.040,0.000,0.000,0.450,0.450,0.000,0.000,0.000,0.750,0.000,0.000,0.000,0.000,0.000,0.000,0.000,0.150,1.000,0.000,0.000,0.000 }, |
|
/* 1 */ { 0.450,0.450,0.450,0.450,0.000,0.000,0.000,0.000,0.300,0.300,0.000,0.000,0.250,0.250,0.000,0.000,0.000,0.750,0.750,0.000,0.000,0.000,0.000,0.400,0.400,0.000,1.000,0.000,0.050,0.050 }, |
|
/* 2 */ { 0.200,0.200,0.500,0.500,0.150,0.150,0.100,0.100,0.150,0.150,0.000,0.000,0.700,0.700,0.000,0.000,0.000,0.750,0.750,0.000,0.200,0.000,0.000,0.000,0.000,0.000,0.850,0.000,0.000,0.000 }, |
|
/* 3 */ { 0.000,0.000,0.000,0.000,0.000,0.000,0.300,0.300,0.000,0.000,0.000,0.000,0.000,0.000,0.100,0.000,0.000,0.000,0.000,0.700,0.300,0.000,0.000,0.200,0.200,0.000,0.000,0.300,0.000,0.000 }, |
|
/* 4 */ { 0.450,0.450,0.000,0.000,0.450,0.450,0.000,0.000,0.000,0.000,0.450,0.450,0.000,0.000,0.000,0.000,0.000,0.000,0.000,0.000,0.000,0.000,0.000,0.000,0.000,0.300,0.000,0.000,0.000,0.000 }, |
|
/* 5 */ { 0.000,0.000,0.350,0.350,0.150,0.150,0.300,0.300,0.450,0.450,0.000,0.000,0.200,0.200,0.000,0.000,0.000,0.000,0.000,0.000,0.000,0.200,0.200,0.000,0.000,0.300,0.000,0.000,0.000,0.000 }, |
|
/* 6 */ { 0.000,0.000,0.650,0.650,0.750,0.750,0.000,0.000,0.000,0.000,0.300,0.300,0.000,0.000,0.000,0.250,0.250,0.000,0.000,0.000,0.000,0.000,0.000,0.000,0.000,0.000,0.000,0.000,0.000,0.000 } |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Changes sequences when shot |
|
//----------------------------------------------------------------------------- |
|
int CFlexCycler::OnTakeDamage( const CTakeDamageInfo &info ) |
|
{ |
|
int nSequence = GetSequence() + 1; |
|
if (!IsValidSequence( nSequence )) |
|
{ |
|
nSequence = 0; |
|
} |
|
|
|
ResetSequence( nSequence ); |
|
m_flCycle = 0; |
|
|
|
return 0; |
|
} |
|
|
|
|
|
void CFlexCycler::SetFlexTarget( LocalFlexController_t flexnum ) |
|
{ |
|
m_flextarget[flexnum] = random->RandomFloat( 0.5, 1.0 ); |
|
|
|
const char *pszType = GetFlexControllerType( flexnum ); |
|
|
|
// zero out all other flexes of the same type |
|
for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++) |
|
{ |
|
if (i != flexnum) |
|
{ |
|
const char *pszOtherType = GetFlexControllerType( i ); |
|
if (stricmp( pszType, pszOtherType ) == 0) |
|
{ |
|
m_flextarget[i] = 0; |
|
} |
|
} |
|
} |
|
|
|
// HACK, for now, consider then linked is named "right_" or "left_" |
|
if (strncmp( "right_", GetFlexControllerName( flexnum ), 6 ) == 0) |
|
{ |
|
m_flextarget[flexnum+1] = m_flextarget[flexnum]; |
|
} |
|
else if (strncmp( "left_", GetFlexControllerName( flexnum ), 5 ) == 0) |
|
{ |
|
m_flextarget[flexnum-1] = m_flextarget[flexnum]; |
|
} |
|
} |
|
|
|
|
|
LocalFlexController_t CFlexCycler::LookupFlex( const char *szTarget ) |
|
{ |
|
for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++) |
|
{ |
|
const char *pszFlex = GetFlexControllerName( i ); |
|
if (stricmp( szTarget, pszFlex ) == 0) |
|
{ |
|
return i; |
|
} |
|
} |
|
return LocalFlexController_t(-1); |
|
} |
|
|
|
|
|
void CFlexCycler::Think( void ) |
|
{ |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
|
|
StudioFrameAdvance ( ); |
|
|
|
if (IsSequenceFinished() && !SequenceLoops()) |
|
{ |
|
// ResetSequenceInfo(); |
|
// hack to avoid reloading model every frame |
|
m_flAnimTime = gpGlobals->curtime; |
|
m_flPlaybackRate = 1.0; |
|
m_bSequenceFinished = false; |
|
m_flLastEventCheck = 0; |
|
m_flCycle = 0; |
|
} |
|
|
|
// only do this if they have more than eyelid movement |
|
if (GetNumFlexControllers() > 2) |
|
{ |
|
const char *pszExpression = flex_expression.GetString(); |
|
|
|
if (pszExpression && pszExpression[0] == '+' && pszExpression[1] != '\0') |
|
{ |
|
int i; |
|
int j = atoi( &pszExpression[1] ); |
|
for ( i = 0; i < GetNumFlexControllers(); i++) |
|
{ |
|
m_flextarget[m_flexnum] = 0; |
|
} |
|
|
|
for (i = 0; i < 35 && predef_flexcontroller_names[i]; i++) |
|
{ |
|
m_flexnum = LookupFlex( predef_flexcontroller_names[i] ); |
|
m_flextarget[m_flexnum] = predef_flexcontroller_values[j][i]; |
|
// Msg( "%s %.3f\n", predef_flexcontroller_names[i], predef_flexcontroller_values[j][i] ); |
|
} |
|
} |
|
else if ( pszExpression && (pszExpression[0] == '1') && (pszExpression[1] == '\0') ) // 1 for maxed controller values |
|
{ |
|
for ( LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++ ) |
|
{ |
|
// Max everything out... |
|
m_flextarget[i] = 1.0f; |
|
SetFlexWeight( i, m_flextarget[i] ); |
|
} |
|
} |
|
else if ( pszExpression && (pszExpression[0] == '^') && (pszExpression[1] == '\0') ) // ^ for sine wave |
|
{ |
|
for ( LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++ ) |
|
{ |
|
// Throw a differently offset sine wave on all of the flex controllers |
|
float fFlexTime = i * (1.0f / (float)GetNumFlexControllers()) + gpGlobals->curtime; |
|
m_flextarget[i] = sinf( fFlexTime ) * 0.5f + 0.5f; |
|
SetFlexWeight( i, m_flextarget[i] ); |
|
} |
|
} |
|
else if (pszExpression && pszExpression[0] != '\0' && strcmp(pszExpression, "+") != 0) |
|
{ |
|
char szExpression[128]; |
|
char szTemp[32]; |
|
|
|
Q_strncpy( szExpression, pszExpression ,sizeof(szExpression)); |
|
char *pszExpression = szExpression; |
|
|
|
while (*pszExpression != '\0') |
|
{ |
|
if (*pszExpression == '+') |
|
*pszExpression = ' '; |
|
|
|
pszExpression++; |
|
} |
|
|
|
pszExpression = szExpression; |
|
|
|
while (*pszExpression) |
|
{ |
|
if (*pszExpression != ' ') |
|
{ |
|
if (*pszExpression == '-') |
|
{ |
|
for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++) |
|
{ |
|
m_flextarget[i] = 0; |
|
} |
|
} |
|
else if (*pszExpression == '?') |
|
{ |
|
for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++) |
|
{ |
|
Msg( "\"%s\" ", GetFlexControllerName( i ) ); |
|
} |
|
Msg( "\n" ); |
|
flex_expression.SetValue( "" ); |
|
} |
|
else |
|
{ |
|
if (sscanf( pszExpression, "%31s", szTemp ) == 1) |
|
{ |
|
m_flexnum = LookupFlex( szTemp ); |
|
|
|
if (m_flexnum != -1 && m_flextarget[m_flexnum] != 1) |
|
{ |
|
m_flextarget[m_flexnum] = 1.0; |
|
// SetFlexTarget( m_flexnum ); |
|
} |
|
pszExpression += strlen( szTemp ) - 1; |
|
} |
|
} |
|
} |
|
pszExpression++; |
|
} |
|
} |
|
else if (m_flextime < gpGlobals->curtime) |
|
{ |
|
// m_flextime = gpGlobals->curtime + 1.0; // RandomFloat( 0.1, 0.5 ); |
|
m_flextime = gpGlobals->curtime + random->RandomFloat( 0.3, 0.5 ) * (30.0 / GetNumFlexControllers()); |
|
m_flexnum = (LocalFlexController_t)random->RandomInt( 0, GetNumFlexControllers() - 1 ); |
|
|
|
// m_flexnum = (pflex->num + 1) % r_psubmodel->numflexes; |
|
|
|
if (m_flextarget[m_flexnum] == 1) |
|
{ |
|
m_flextarget[m_flexnum] = 0; |
|
// pflex->time = cl.time + 0.1; |
|
} |
|
else if (stricmp( GetFlexControllerType( m_flexnum ), "phoneme" ) != 0) |
|
{ |
|
if (strstr( GetFlexControllerName( m_flexnum ), "upper_raiser" ) == NULL) |
|
{ |
|
Msg( "%s:%s\n", GetFlexControllerType( m_flexnum ), GetFlexControllerName( m_flexnum ) ); |
|
SetFlexTarget( m_flexnum ); |
|
} |
|
} |
|
|
|
#if 0 |
|
char szWhat[256]; |
|
szWhat[0] = '\0'; |
|
for (int i = 0; i < GetNumFlexControllers(); i++) |
|
{ |
|
if (m_flextarget[i] == 1.0) |
|
{ |
|
if (stricmp( GetFlexFacs( i ), "upper") != 0 && stricmp( GetFlexFacs( i ), "lower") != 0) |
|
{ |
|
if (szWhat[0] == '\0') |
|
Q_strncat( szWhat, "-", sizeof( szWhat ), COPY_ALL_CHARACTERS ); |
|
else |
|
Q_strncat( szWhat, "+", sizeof( szWhat ), COPY_ALL_CHARACTERS ); |
|
Q_strncat( szWhat, GetFlexFacs( i ), sizeof( szWhat ), COPY_ALL_CHARACTERS ); |
|
} |
|
} |
|
} |
|
Msg( "%s\n", szWhat ); |
|
#endif |
|
} |
|
|
|
// slide it up. |
|
for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++) |
|
{ |
|
float weight = GetFlexWeight( i ); |
|
|
|
if (weight != m_flextarget[i]) |
|
{ |
|
weight = weight + (m_flextarget[i] - weight) / random->RandomFloat( 2.0, 4.0 ); |
|
} |
|
weight = clamp( weight, 0.0f, 1.0f ); |
|
SetFlexWeight( i, weight ); |
|
} |
|
|
|
#if 1 |
|
if (flex_talk.GetInt() == -1) |
|
{ |
|
m_istalking = 1; |
|
char pszSentence[256]; |
|
Q_snprintf( pszSentence,sizeof(pszSentence), "%s%d", STRING(m_iszSentence), m_sentence++ ); |
|
int sentenceIndex = engine->SentenceIndexFromName( pszSentence ); |
|
if (sentenceIndex >= 0) |
|
{ |
|
Msg( "%d : %s\n", sentenceIndex, pszSentence ); |
|
CPASAttenuationFilter filter( this ); |
|
CBaseEntity::EmitSentenceByIndex( filter, entindex(), CHAN_VOICE, sentenceIndex, 1, SNDLVL_TALKING, 0, PITCH_NORM ); |
|
} |
|
else |
|
{ |
|
m_sentence = 0; |
|
} |
|
|
|
flex_talk.SetValue( "0" ); |
|
} |
|
else if (!FStrEq( flex_talk.GetString(), "0") ) |
|
{ |
|
int sentenceIndex = engine->SentenceIndexFromName( flex_talk.GetString() ); |
|
if (sentenceIndex >= 0) |
|
{ |
|
CPASAttenuationFilter filter( this ); |
|
CBaseEntity::EmitSentenceByIndex( filter, entindex(), CHAN_VOICE, sentenceIndex, 1, SNDLVL_TALKING, 0, PITCH_NORM ); |
|
} |
|
flex_talk.SetValue( "0" ); |
|
} |
|
#else |
|
if (flex_talk.GetInt()) |
|
{ |
|
if (m_speaktime < gpGlobals->curtime) |
|
{ |
|
if (m_phoneme == 0) |
|
{ |
|
for (m_phoneme = 0; m_phoneme < GetNumFlexControllers(); m_phoneme++) |
|
{ |
|
if (stricmp( GetFlexFacs( m_phoneme ), "27") == 0) |
|
break; |
|
} |
|
} |
|
m_istalking = !m_istalking; |
|
if (m_istalking) |
|
{ |
|
m_looktime = gpGlobals->curtime - 1.0; |
|
m_speaktime = gpGlobals->curtime + random->RandomFloat( 0.5, 2.0 ); |
|
} |
|
else |
|
{ |
|
m_speaktime = gpGlobals->curtime + random->RandomFloat( 1.0, 3.0 ); |
|
} |
|
} |
|
|
|
for (i = m_phoneme; i < GetNumFlexControllers(); i++) |
|
{ |
|
SetFlexWeight( i, 0.0f ); |
|
} |
|
|
|
if (m_istalking) |
|
{ |
|
m_flextime = gpGlobals->curtime + random->RandomFloat( 0.0, 0.2 ); |
|
m_flexWeight[random->RandomInt(m_phoneme, GetNumFlexControllers()-1)] = random->RandomFloat( 0.5, 1.0 ); |
|
float mouth = random->RandomFloat( 0.0, 1.0 ); |
|
float jaw = random->RandomFloat( 0.0, 1.0 ); |
|
|
|
m_flexWeight[m_phoneme - 2] = jaw * (mouth); |
|
m_flexWeight[m_phoneme - 1] = jaw * (1.0 - mouth); |
|
} |
|
} |
|
else |
|
{ |
|
m_istalking = 0; |
|
} |
|
#endif |
|
|
|
// blink |
|
if (m_blinktime < gpGlobals->curtime) |
|
{ |
|
Blink(); |
|
m_blinktime = gpGlobals->curtime + random->RandomFloat( 1.5, 4.5 ); |
|
} |
|
} |
|
|
|
|
|
Vector forward, right, up; |
|
GetVectors( &forward, &right, &up ); |
|
|
|
CBaseEntity *pPlayer = (CBaseEntity *)UTIL_GetLocalPlayer(); |
|
if (pPlayer) |
|
{ |
|
if (pPlayer->GetSmoothedVelocity().Length() != 0 && DotProduct( forward, pPlayer->EyePosition() - EyePosition()) > 0.5) |
|
{ |
|
m_lookTarget = pPlayer->EyePosition(); |
|
m_looktime = gpGlobals->curtime + random->RandomFloat(2.0,4.0); |
|
} |
|
else if (m_looktime < gpGlobals->curtime) |
|
{ |
|
if ((!m_istalking) && random->RandomInt( 0, 1 ) == 0) |
|
{ |
|
m_lookTarget = EyePosition() + forward * 128 + right * random->RandomFloat(-64,64) + up * random->RandomFloat(-32,32); |
|
m_looktime = gpGlobals->curtime + random->RandomFloat(0.3,1.0); |
|
|
|
if (m_blinktime - 0.5 < gpGlobals->curtime) |
|
{ |
|
Blink(); |
|
} |
|
} |
|
else |
|
{ |
|
m_lookTarget = pPlayer->EyePosition(); |
|
m_looktime = gpGlobals->curtime + random->RandomFloat(1.0,4.0); |
|
} |
|
} |
|
|
|
#if 0 |
|
float dt = acos( DotProduct( (m_lookTarget - EyePosition()).Normalize(), (m_viewtarget - EyePosition()).Normalize() ) ); |
|
|
|
if (dt > M_PI / 4) |
|
{ |
|
dt = (M_PI / 4) * dt; |
|
m_viewtarget = ((1 - dt) * m_viewtarget + dt * m_lookTarget); |
|
} |
|
#endif |
|
|
|
SetViewtarget( m_lookTarget ); |
|
} |
|
|
|
// Handle any facial animation from scene playback |
|
// FIXME: do we still actually need flex cyclers? |
|
// AddSceneSceneEvents(); |
|
} |
|
|
|
|
|
void CFlexCycler::ProcessSceneEvents( void ) |
|
{ |
|
// Don't do anything since we handle facial stuff in Think() |
|
} |
|
|
|
|
|
BEGIN_BYTESWAP_DATADESC( flexsettinghdr_t ) |
|
DEFINE_FIELD( id, FIELD_INTEGER ), |
|
DEFINE_FIELD( version, FIELD_INTEGER ), |
|
DEFINE_ARRAY( name, FIELD_CHARACTER, 64 ), |
|
DEFINE_FIELD( length, FIELD_INTEGER ), |
|
DEFINE_FIELD( numflexsettings, FIELD_INTEGER ), |
|
DEFINE_FIELD( flexsettingindex, FIELD_INTEGER ), |
|
DEFINE_FIELD( nameindex, FIELD_INTEGER ), |
|
DEFINE_FIELD( numindexes, FIELD_INTEGER ), |
|
DEFINE_FIELD( indexindex, FIELD_INTEGER ), |
|
DEFINE_FIELD( numkeys, FIELD_INTEGER ), |
|
DEFINE_FIELD( keynameindex, FIELD_INTEGER ), |
|
DEFINE_FIELD( keymappingindex, FIELD_INTEGER ), |
|
END_BYTESWAP_DATADESC() |
|
|
|
BEGIN_BYTESWAP_DATADESC( flexsetting_t ) |
|
DEFINE_FIELD( nameindex, FIELD_INTEGER ), |
|
DEFINE_FIELD( obsolete1, FIELD_INTEGER ), |
|
DEFINE_FIELD( numsettings, FIELD_INTEGER ), |
|
DEFINE_FIELD( index, FIELD_INTEGER ), |
|
DEFINE_FIELD( obsolete2, FIELD_INTEGER ), |
|
DEFINE_FIELD( settingindex, FIELD_INTEGER ), |
|
END_BYTESWAP_DATADESC() |
|
|
|
BEGIN_BYTESWAP_DATADESC( flexweight_t ) |
|
DEFINE_FIELD( key, FIELD_INTEGER ), |
|
DEFINE_FIELD( weight, FIELD_FLOAT ), |
|
DEFINE_FIELD( influence, FIELD_FLOAT ), |
|
END_BYTESWAP_DATADESC() |
|
|
|
|