From 39ed87570bdb2f86969d4be821c94b722dc71179 Mon Sep 17 00:00:00 2001 From: Joe Ludwig Date: Wed, 26 Jun 2013 15:22:04 -0700 Subject: First version of the SOurce SDK 2013 --- mp/src/game/server/baseflex.cpp | 2796 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 2796 insertions(+) create mode 100644 mp/src/game/server/baseflex.cpp (limited to 'mp/src/game/server/baseflex.cpp') diff --git a/mp/src/game/server/baseflex.cpp b/mp/src/game/server/baseflex.cpp new file mode 100644 index 00000000..f173a04d --- /dev/null +++ b/mp/src/game/server/baseflex.cpp @@ -0,0 +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() + -- cgit v1.2.3