diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/baseflex.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/baseflex.cpp')
| -rw-r--r-- | mp/src/game/server/baseflex.cpp | 5592 |
1 files changed, 2796 insertions, 2796 deletions
diff --git a/mp/src/game/server/baseflex.cpp b/mp/src/game/server/baseflex.cpp index f173a04d..8e17fa75 100644 --- a/mp/src/game/server/baseflex.cpp +++ b/mp/src/game/server/baseflex.cpp @@ -1,2796 +1,2796 @@ -//========= 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, " " );
- if ( pszAct )
- {
- char szActName[256];
- 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()
-
+//========= 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, " " ); + if ( pszAct ) + { + char szActName[256]; + 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() + |