From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- sp/src/game/server/baseflex.cpp | 5592 +++++++++++++++++++-------------------- 1 file changed, 2796 insertions(+), 2796 deletions(-) (limited to 'sp/src/game/server/baseflex.cpp') diff --git a/sp/src/game/server/baseflex.cpp b/sp/src/game/server/baseflex.cpp index f173a04d..8e17fa75 100644 --- a/sp/src/game/server/baseflex.cpp +++ b/sp/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(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(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() + -- cgit v1.2.3