//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include #include "baseflex.h" #include "entitylist.h" #include "choreoevent.h" #include "choreoactor.h" #include "choreochannel.h" #include "choreoscene.h" #include "studio.h" #include "networkstringtable_gamedll.h" #include "ai_basenpc.h" #include "engine/IEngineSound.h" #include "ai_navigator.h" #include "saverestore_utlvector.h" #include "ai_baseactor.h" #include "AI_Criteria.h" #include "tier1/strtools.h" #include "checksum_crc.h" #include "SoundEmitterSystem/isoundemittersystembase.h" #include "utlbuffer.h" #include "tier0/icommandline.h" #include "sceneentity.h" #include "datacache/idatacache.h" #include "dt_utlvector_send.h" #include "ichoreoeventcallback.h" #include "scenefilecache/ISceneFileCache.h" #include "SceneCache.h" #include "scripted.h" #include "basemultiplayerplayer.h" #include "env_debughistory.h" #include "datacache/imdlcache.h" #include "recipientfilter.h" #include "team.h" #include "triggers.h" #ifdef HL2_EPISODIC #include "npc_alyx_episodic.h" #endif // HL2_EPISODIC // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" extern ISoundEmitterSystemBase *soundemitterbase; extern ISceneFileCache *scenefilecache; // From SoundEmitterSystem.cpp bool GetCaptionHash( char const *pchStringName, bool bWarnIfMissing, unsigned int &hash ); bool CanEmitCaption( unsigned int hash ); class CSceneEntity; class CBaseFlex; // VCDS are loaded from their compiled/binary format (much faster) // Requies vcds be saved as compiled assets //#define COMPILED_VCDS 1 static ConVar scene_forcecombined( "scene_forcecombined", "0", 0, "When playing back, force use of combined .wav files even in english." ); static ConVar scene_maxcaptionradius( "scene_maxcaptionradius", "1200", 0, "Only show closed captions if recipient is within this many units of speaking actor (0==disabled)." ); ConVar scene_clientplayback( "scene_clientplayback", "1", 0, "Play all vcds on the clients." ); // Assume sound system is 100 msec lagged (only used if we can't find snd_mixahead cvar!) #define SOUND_SYSTEM_LATENCY_DEFAULT ( 0.1f ) // Think every 50 msec (FIXME: Try 10hz?) #define SCENE_THINK_INTERVAL 0.001 // FIXME: make scene's think in concert with their npc's #define FINDNAMEDENTITY_MAX_ENTITIES 32 // max number of entities to be considered for random entity selection in FindNamedEntity // List of the last 5 lines of speech from NPCs for bug reports static recentNPCSpeech_t speechListSounds[ SPEECH_LIST_MAX_SOUNDS ] = { { 0, "", "" }, { 0, "", "" }, { 0, "", "" }, { 0, "", "" }, { 0, "", "" } }; static int speechListIndex = 0; // Only allow scenes to change their pitch within a range of values #define SCENE_MIN_PITCH 0.25f #define SCENE_MAX_PITCH 2.5f //=========================================================================================================== // SCENE LIST MANAGER //=========================================================================================================== #define SCENE_LIST_MANAGER_MAX_SCENES 16 //----------------------------------------------------------------------------- // Purpose: Entity that manages a list of scenes //----------------------------------------------------------------------------- class CSceneListManager : public CLogicalEntity { DECLARE_CLASS( CSceneListManager, CLogicalEntity ); public: DECLARE_DATADESC(); virtual void Activate( void ); void ShutdownList( void ); void SceneStarted( CBaseEntity *pSceneOrManager ); void AddListManager( CSceneListManager *pManager ); void RemoveScene( int iIndex ); // Inputs void InputShutdown( inputdata_t &inputdata ); private: CUtlVector< CHandle< CSceneListManager > > m_hListManagers; string_t m_iszScenes[SCENE_LIST_MANAGER_MAX_SCENES]; EHANDLE m_hScenes[SCENE_LIST_MANAGER_MAX_SCENES]; }; //----------------------------------------------------------------------------- // Purpose: This class exists solely to call think on all scene entities in a deterministic order //----------------------------------------------------------------------------- class CSceneManager : public CBaseEntity { DECLARE_CLASS( CSceneManager, CBaseEntity ); DECLARE_DATADESC(); public: virtual void Spawn() { BaseClass::Spawn(); SetNextThink( gpGlobals->curtime ); } virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() | FCAP_DONT_SAVE; } virtual void Think(); void ClearAllScenes(); void AddSceneEntity( CSceneEntity *scene ); void RemoveSceneEntity( CSceneEntity *scene ); void QueueRestoredSound( CBaseFlex *actor, char const *soundname, soundlevel_t soundlevel, float time_in_past ); void OnClientActive( CBasePlayer *player ); void RemoveActorFromScenes( CBaseFlex *pActor, bool bInstancedOnly, bool bNonIdleOnly, const char *pszThisSceneOnly ); void PauseActorsScenes( CBaseFlex *pActor, bool bInstancedOnly ); bool IsInInterruptableScenes( CBaseFlex *pActor ); void ResumeActorsScenes( CBaseFlex *pActor, bool bInstancedOnly ); void QueueActorsScenesToResume( CBaseFlex *pActor, bool bInstancedOnly ); bool IsRunningScriptedScene( CBaseFlex *pActor, bool bIgnoreInstancedScenes ); bool IsRunningScriptedSceneAndNotPaused( CBaseFlex *pActor, bool bIgnoreInstancedScenes ); bool IsRunningScriptedSceneWithSpeech( CBaseFlex *pActor, bool bIgnoreInstancedScenes ); bool IsRunningScriptedSceneWithSpeechAndNotPaused( CBaseFlex *pActor, bool bIgnoreInstancedScenes ); private: struct CRestoreSceneSound { CRestoreSceneSound() { actor = NULL; soundname[ 0 ] = NULL; soundlevel = SNDLVL_NORM; time_in_past = 0.0f; } CHandle< CBaseFlex > actor; char soundname[ 128 ]; soundlevel_t soundlevel; float time_in_past; }; CUtlVector< CHandle< CSceneEntity > > m_ActiveScenes; CUtlVector< CRestoreSceneSound > m_QueuedSceneSounds; }; //--------------------------------------------------------- // Save/Restore //--------------------------------------------------------- BEGIN_DATADESC( CSceneManager ) DEFINE_UTLVECTOR( m_ActiveScenes, FIELD_EHANDLE ), // DEFINE_FIELD( m_QueuedSceneSounds, CUtlVector < CRestoreSceneSound > ), // Don't save/restore this, it's created and used by OnRestore only END_DATADESC() #ifdef DISABLE_DEBUG_HISTORY #define LocalScene_Printf Scene_Printf #else //----------------------------------------------------------------------------- // Purpose: // Input : *pFormat - // ... - // Output : static void //----------------------------------------------------------------------------- void LocalScene_Printf( const char *pFormat, ... ) { va_list marker; char msg[8192]; va_start(marker, pFormat); Q_vsnprintf(msg, sizeof(msg), pFormat, marker); va_end(marker); Scene_Printf( "%s", msg ); ADD_DEBUG_HISTORY( HISTORY_SCENE_PRINT, UTIL_VarArgs( "(%0.2f) %s", gpGlobals->curtime, msg ) ); } #endif //----------------------------------------------------------------------------- // Purpose: // Input : *filename - // **buffer - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CopySceneFileIntoMemory( char const *pFilename, byte **pBuffer, int *pSize ) { size_t bufSize = scenefilecache->GetSceneBufferSize( pFilename ); if ( bufSize > 0 ) { *pBuffer = new byte[bufSize]; *pSize = bufSize; return scenefilecache->GetSceneData( pFilename, (byte *)(*pBuffer), bufSize ); } *pBuffer = 0; *pSize = 0; return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void FreeSceneFileMemory( byte *buffer ) { delete[] buffer; } //----------------------------------------------------------------------------- // Binary compiled VCDs get their strings from a pool //----------------------------------------------------------------------------- class CChoreoStringPool : public IChoreoStringPool { public: short FindOrAddString( const char *pString ) { // huh?, no compilation at run time, only fetches Assert( 0 ); return -1; } bool GetString( short stringId, char *buff, int buffSize ) { // fetch from compiled pool const char *pString = scenefilecache->GetSceneString( stringId ); if ( !pString ) { V_strncpy( buff, "", buffSize ); return false; } V_strncpy( buff, pString, buffSize ); return true; } }; CChoreoStringPool g_ChoreoStringPool; //----------------------------------------------------------------------------- // Purpose: Singleton scene manager. Created by first placed scene or recreated it it's deleted for some unknown reason // Output : CSceneManager //----------------------------------------------------------------------------- CSceneManager *GetSceneManager() { // Create it if it doesn't exist static CHandle< CSceneManager > s_SceneManager; if ( s_SceneManager == NULL ) { s_SceneManager = ( CSceneManager * )CreateEntityByName( "scene_manager" ); Assert( s_SceneManager ); if ( s_SceneManager ) { s_SceneManager->Spawn(); } } Assert( s_SceneManager ); return s_SceneManager; } //----------------------------------------------------------------------------- // Purpose: // Input : *player - //----------------------------------------------------------------------------- void SceneManager_ClientActive( CBasePlayer *player ) { Assert( GetSceneManager() ); if ( GetSceneManager() ) { GetSceneManager()->OnClientActive( player ); } } //----------------------------------------------------------------------------- // Purpose: FIXME, need to deal with save/restore //----------------------------------------------------------------------------- class CSceneEntity : public CPointEntity, public IChoreoEventCallback { friend class CInstancedSceneEntity; public: enum { SCENE_ACTION_UNKNOWN = 0, SCENE_ACTION_CANCEL, SCENE_ACTION_RESUME, }; enum { SCENE_BUSYACTOR_DEFAULT = 0, SCENE_BUSYACTOR_WAIT, SCENE_BUSYACTOR_INTERRUPT, SCENE_BUSYACTOR_INTERRUPT_CANCEL, }; DECLARE_CLASS( CSceneEntity, CPointEntity ); DECLARE_SERVERCLASS(); // script description DECLARE_ENT_SCRIPTDESC(); CSceneEntity( void ); ~CSceneEntity( void ); // From IChoreoEventCallback virtual void StartEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ); virtual void EndEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ); virtual void ProcessEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ); virtual bool CheckEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ); virtual int UpdateTransmitState(); virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo ); void SetRecipientFilter( IRecipientFilter *filter ); virtual void Activate(); virtual void Precache( void ); virtual void Spawn( void ); virtual void UpdateOnRemove( void ); virtual void OnRestore(); virtual void OnLoaded(); virtual int DrawDebugTextOverlays(); DECLARE_DATADESC(); virtual void OnSceneFinished( bool canceled, bool fireoutput ); virtual void DoThink( float frametime ); virtual void PauseThink( void ); bool IsPlayingBack() const { return m_bIsPlayingBack; } bool IsPaused() const { return m_bPaused; } bool IsMultiplayer() const { return m_bMultiplayer; } bool IsInterruptable(); virtual void ClearInterrupt(); virtual void CheckInterruptCompletion(); virtual bool InterruptThisScene( CSceneEntity *otherScene ); void RequestCompletionNotification( CSceneEntity *otherScene ); virtual void NotifyOfCompletion( CSceneEntity *interruptor ); void AddListManager( CSceneListManager *pManager ); void ClearActivatorTargets( void ); void SetBreakOnNonIdle( bool bBreakOnNonIdle ) { m_bBreakOnNonIdle = bBreakOnNonIdle; } bool ShouldBreakOnNonIdle( void ) { return m_bBreakOnNonIdle; } // Inputs void InputStartPlayback( inputdata_t &inputdata ); void InputPausePlayback( inputdata_t &inputdata ); void InputResumePlayback( inputdata_t &inputdata ); void InputCancelPlayback( inputdata_t &inputdata ); void InputCancelAtNextInterrupt( inputdata_t &inputdata ); void InputPitchShiftPlayback( inputdata_t &inputdata ); void InputTriggerEvent( inputdata_t &inputdata ); // If the scene is playing, finds an actor in the scene who can respond to the specified concept token void InputInterjectResponse( inputdata_t &inputdata ); // If this scene is waiting on an actor, give up and quit trying. void InputStopWaitingForActor( inputdata_t &inputdata ); virtual void StartPlayback( void ); virtual void PausePlayback( void ); virtual void ResumePlayback( void ); virtual void CancelPlayback( void ); virtual void PitchShiftPlayback( float fPitch ); virtual void QueueResumePlayback( void ); bool ValidScene() const; // Scene load/unload static CChoreoScene *LoadScene( const char *filename, IChoreoEventCallback *pCallback ); void UnloadScene( void ); struct SpeakEventSound_t { CUtlSymbol m_Symbol; float m_flStartTime; }; static bool SpeakEventSoundLessFunc( const SpeakEventSound_t& lhs, const SpeakEventSound_t& rhs ); bool GetSoundNameForPlayer( CChoreoEvent *event, CBasePlayer *player, char *buf, size_t buflen ); void BuildSortedSpeakEventSoundsPrefetchList( CChoreoScene *scene, CUtlSymbolTable& table, CUtlRBTree< SpeakEventSound_t >& soundnames, float timeOffset ); void PrefetchSpeakEventSounds( CUtlSymbolTable& table, CUtlRBTree< SpeakEventSound_t >& soundnames ); // Event handlers virtual void DispatchStartExpression( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ); virtual void DispatchEndExpression( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ); virtual void DispatchStartFlexAnimation( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ); virtual void DispatchEndFlexAnimation( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ); virtual void DispatchStartGesture( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ); virtual void DispatchEndGesture( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ); virtual void DispatchStartLookAt( CChoreoScene *scene, CBaseFlex *actor, CBaseEntity *actor2, CChoreoEvent *event ); virtual void DispatchEndLookAt( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ); virtual void DispatchStartMoveTo( CChoreoScene *scene, CBaseFlex *actor, CBaseEntity *actor2, CChoreoEvent *event ); virtual void DispatchEndMoveTo( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ); virtual void DispatchStartSpeak( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event, soundlevel_t iSoundlevel ); virtual void DispatchEndSpeak( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ); virtual void DispatchStartFace( CChoreoScene *scene, CBaseFlex *actor, CBaseEntity *actor2, CChoreoEvent *event ); virtual void DispatchEndFace( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ); virtual void DispatchStartSequence( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ); virtual void DispatchEndSequence( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ); virtual void DispatchStartSubScene( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ); virtual void DispatchStartInterrupt( CChoreoScene *scene, CChoreoEvent *event ); virtual void DispatchEndInterrupt( CChoreoScene *scene, CChoreoEvent *event ); virtual void DispatchStartGeneric( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ); virtual void DispatchEndGeneric( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ); // NPC can play interstitial vcds (such as responding to the player doing something during a scene) virtual void DispatchStartPermitResponses( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ); virtual void DispatchEndPermitResponses( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ); // Global events virtual void DispatchProcessLoop( CChoreoScene *scene, CChoreoEvent *event ); virtual void DispatchPauseScene( CChoreoScene *scene, const char *parameters ); virtual void DispatchStopPoint( CChoreoScene *scene, const char *parameters ); virtual float EstimateLength( void ); void CancelIfSceneInvolvesActor( CBaseEntity *pActor ); bool InvolvesActor( CBaseEntity *pActor ); // NOTE: returns false if scene hasn't loaded yet void GenerateSoundScene( CBaseFlex *pActor, const char *soundname ); virtual float GetPostSpeakDelay() { return 1.0; } bool HasUnplayedSpeech( void ); bool HasFlexAnimation( void ); void SetCurrentTime( float t, bool forceClientSync ); void InputScriptPlayerDeath( inputdata_t &inputdata ); void AddBroadcastTeamTarget( int nTeamIndex ); void RemoveBroadcastTeamTarget( int nTeamIndex ); // Data public: string_t m_iszSceneFile; string_t m_iszResumeSceneFile; EHANDLE m_hWaitingForThisResumeScene; bool m_bWaitingForResumeScene; string_t m_iszTarget1; string_t m_iszTarget2; string_t m_iszTarget3; string_t m_iszTarget4; string_t m_iszTarget5; string_t m_iszTarget6; string_t m_iszTarget7; string_t m_iszTarget8; EHANDLE m_hTarget1; EHANDLE m_hTarget2; EHANDLE m_hTarget3; EHANDLE m_hTarget4; EHANDLE m_hTarget5; EHANDLE m_hTarget6; EHANDLE m_hTarget7; EHANDLE m_hTarget8; CNetworkVar( bool, m_bIsPlayingBack ); CNetworkVar( bool, m_bPaused ); CNetworkVar( bool, m_bMultiplayer ); CNetworkVar( float, m_flForceClientTime ); float m_flCurrentTime; float m_flFrameTime; bool m_bCancelAtNextInterrupt; float m_fPitch; bool m_bAutomated; int m_nAutomatedAction; float m_flAutomationDelay; float m_flAutomationTime; // A pause from an input requires another input to unpause (it's a hard pause) bool m_bPausedViaInput; // Waiting for the actor to be able to speak. bool m_bWaitingForActor; // Waiting for a point at which we can interrupt our actors bool m_bWaitingForInterrupt; bool m_bInterruptedActorsScenes; bool m_bBreakOnNonIdle; public: virtual CBaseFlex *FindNamedActor( int index ); virtual CBaseFlex *FindNamedActor( CChoreoActor *pChoreoActor ); virtual CBaseFlex *FindNamedActor( const char *name ); virtual CBaseEntity *FindNamedEntity( const char *name, CBaseEntity *pActor = NULL, bool bBaseFlexOnly = false, bool bUseClear = false ); CBaseEntity *FindNamedTarget( string_t iszTarget, bool bBaseFlexOnly = false ); virtual CBaseEntity *FindNamedEntityClosest( const char *name, CBaseEntity *pActor = NULL, bool bBaseFlexOnly = false, bool bUseClear = false, const char *pszSecondary = NULL ); HSCRIPT ScriptFindNamedEntity( const char *name ); bool ScriptLoadSceneFromString( const char * pszFilename, const char *pszData ); private: CUtlVector< CHandle< CBaseFlex > > m_hActorList; CUtlVector< CHandle< CBaseEntity > > m_hRemoveActorList; private: inline void SetRestoring( bool bRestoring ); // Prevent derived classed from using this! virtual void Think( void ) {}; void ClearSceneEvents( CChoreoScene *scene, bool canceled ); void ClearSchedules( CChoreoScene *scene ); float GetSoundSystemLatency( void ); void PrecacheScene( CChoreoScene *scene ); CChoreoScene *GenerateSceneForSound( CBaseFlex *pFlexActor, const char *soundname ); bool CheckActors(); void PrefetchAnimBlocks( CChoreoScene *scene ); bool ShouldNetwork() const; // Set if we tried to async the scene but the FS returned that the data was not loadable bool m_bSceneMissing; CChoreoScene *m_pScene; CNetworkVar( int, m_nSceneStringIndex ); static const ConVar *m_pcvSndMixahead; COutputEvent m_OnStart; COutputEvent m_OnCompletion; COutputEvent m_OnCanceled; COutputEvent m_OnTrigger1; COutputEvent m_OnTrigger2; COutputEvent m_OnTrigger3; COutputEvent m_OnTrigger4; COutputEvent m_OnTrigger5; COutputEvent m_OnTrigger6; COutputEvent m_OnTrigger7; COutputEvent m_OnTrigger8; COutputEvent m_OnTrigger9; COutputEvent m_OnTrigger10; COutputEvent m_OnTrigger11; COutputEvent m_OnTrigger12; COutputEvent m_OnTrigger13; COutputEvent m_OnTrigger14; COutputEvent m_OnTrigger15; COutputEvent m_OnTrigger16; int m_nInterruptCount; bool m_bInterrupted; CHandle< CSceneEntity > m_hInterruptScene; bool m_bCompletedEarly; bool m_bInterruptSceneFinished; CUtlVector< CHandle< CSceneEntity > > m_hNotifySceneCompletion; CUtlVector< CHandle< CSceneListManager > > m_hListManagers; bool m_bRestoring; bool m_bGenerated; string_t m_iszSoundName; CHandle< CBaseFlex > m_hActor; EHANDLE m_hActivator; int m_BusyActor; int m_iPlayerDeathBehavior; CRecipientFilter *m_pRecipientFilter; public: void SetBackground( bool bIsBackground ); bool IsBackground( void ); }; const ConVar *CSceneEntity::m_pcvSndMixahead = NULL; LINK_ENTITY_TO_CLASS( logic_choreographed_scene, CSceneEntity ); LINK_ENTITY_TO_CLASS( scripted_scene, CSceneEntity ); IMPLEMENT_SERVERCLASS_ST_NOBASE( CSceneEntity, DT_SceneEntity ) SendPropInt(SENDINFO(m_nSceneStringIndex),MAX_CHOREO_SCENES_STRING_BITS,SPROP_UNSIGNED), SendPropBool(SENDINFO(m_bIsPlayingBack)), SendPropBool(SENDINFO(m_bPaused)), SendPropBool(SENDINFO(m_bMultiplayer)), SendPropFloat(SENDINFO(m_flForceClientTime)), SendPropUtlVector( SENDINFO_UTLVECTOR( m_hActorList ), MAX_ACTORS_IN_SCENE, // max elements SendPropEHandle( NULL, 0 ) ), END_SEND_TABLE() BEGIN_DATADESC( CSceneEntity ) // Keys DEFINE_KEYFIELD( m_iszSceneFile, FIELD_STRING, "SceneFile" ), DEFINE_KEYFIELD( m_iszResumeSceneFile, FIELD_STRING, "ResumeSceneFile" ), DEFINE_FIELD( m_hWaitingForThisResumeScene, FIELD_EHANDLE ), DEFINE_FIELD( m_bWaitingForResumeScene, FIELD_BOOLEAN ), DEFINE_KEYFIELD( m_iszTarget1, FIELD_STRING, "target1" ), DEFINE_KEYFIELD( m_iszTarget2, FIELD_STRING, "target2" ), DEFINE_KEYFIELD( m_iszTarget3, FIELD_STRING, "target3" ), DEFINE_KEYFIELD( m_iszTarget4, FIELD_STRING, "target4" ), DEFINE_KEYFIELD( m_iszTarget5, FIELD_STRING, "target5" ), DEFINE_KEYFIELD( m_iszTarget6, FIELD_STRING, "target6" ), DEFINE_KEYFIELD( m_iszTarget7, FIELD_STRING, "target7" ), DEFINE_KEYFIELD( m_iszTarget8, FIELD_STRING, "target8" ), DEFINE_KEYFIELD( m_BusyActor, FIELD_INTEGER, "busyactor" ), DEFINE_FIELD( m_hTarget1, FIELD_EHANDLE ), DEFINE_FIELD( m_hTarget2, FIELD_EHANDLE ), DEFINE_FIELD( m_hTarget3, FIELD_EHANDLE ), DEFINE_FIELD( m_hTarget4, FIELD_EHANDLE ), DEFINE_FIELD( m_hTarget5, FIELD_EHANDLE ), DEFINE_FIELD( m_hTarget6, FIELD_EHANDLE ), DEFINE_FIELD( m_hTarget7, FIELD_EHANDLE ), DEFINE_FIELD( m_hTarget8, FIELD_EHANDLE ), DEFINE_FIELD( m_bIsPlayingBack, FIELD_BOOLEAN ), DEFINE_FIELD( m_bPaused, FIELD_BOOLEAN ), DEFINE_FIELD( m_flCurrentTime, FIELD_FLOAT ), // relative, not absolute time DEFINE_FIELD( m_flForceClientTime, FIELD_FLOAT ), DEFINE_FIELD( m_flFrameTime, FIELD_FLOAT ), // last frametime DEFINE_FIELD( m_bCancelAtNextInterrupt, FIELD_BOOLEAN ), DEFINE_FIELD( m_fPitch, FIELD_FLOAT ), DEFINE_FIELD( m_bAutomated, FIELD_BOOLEAN ), DEFINE_FIELD( m_nAutomatedAction, FIELD_INTEGER ), DEFINE_FIELD( m_flAutomationDelay, FIELD_FLOAT ), DEFINE_FIELD( m_flAutomationTime, FIELD_FLOAT ), // relative, not absolute time DEFINE_FIELD( m_bPausedViaInput, FIELD_BOOLEAN ), DEFINE_FIELD( m_bWaitingForActor, FIELD_BOOLEAN ), DEFINE_FIELD( m_bWaitingForInterrupt, FIELD_BOOLEAN ), DEFINE_FIELD( m_bInterruptedActorsScenes, FIELD_BOOLEAN ), DEFINE_FIELD( m_bBreakOnNonIdle, FIELD_BOOLEAN ), DEFINE_UTLVECTOR( m_hActorList, FIELD_EHANDLE ), DEFINE_UTLVECTOR( m_hRemoveActorList, FIELD_EHANDLE ), // DEFINE_FIELD( m_pScene, FIELD_XXXX ) // Special processing used for this // These are set up in the constructor // DEFINE_FIELD( m_pcvSndMixahead, FIELD_XXXXX ), // DEFINE_FIELD( m_bRestoring, FIELD_BOOLEAN ), DEFINE_FIELD( m_nInterruptCount, FIELD_INTEGER ), DEFINE_FIELD( m_bInterrupted, FIELD_BOOLEAN ), DEFINE_FIELD( m_hInterruptScene, FIELD_EHANDLE ), DEFINE_FIELD( m_bCompletedEarly, FIELD_BOOLEAN ), DEFINE_FIELD( m_bInterruptSceneFinished, FIELD_BOOLEAN ), DEFINE_FIELD( m_bGenerated, FIELD_BOOLEAN ), DEFINE_FIELD( m_iszSoundName, FIELD_STRING ), DEFINE_FIELD( m_hActor, FIELD_EHANDLE ), DEFINE_FIELD( m_hActivator, FIELD_EHANDLE ), // DEFINE_FIELD( m_bSceneMissing, FIELD_BOOLEAN ), DEFINE_UTLVECTOR( m_hNotifySceneCompletion, FIELD_EHANDLE ), DEFINE_UTLVECTOR( m_hListManagers, FIELD_EHANDLE ), DEFINE_FIELD( m_bMultiplayer, FIELD_BOOLEAN ), // DEFINE_FIELD( m_nSceneStringIndex, FIELD_INTEGER ), // DEFINE_FIELD( m_pRecipientFilter, IRecipientFilter* ), // Multiplayer only // Inputs DEFINE_INPUTFUNC( FIELD_VOID, "Start", InputStartPlayback ), DEFINE_INPUTFUNC( FIELD_VOID, "Pause", InputPausePlayback ), DEFINE_INPUTFUNC( FIELD_VOID, "Resume", InputResumePlayback ), DEFINE_INPUTFUNC( FIELD_VOID, "Cancel", InputCancelPlayback ), DEFINE_INPUTFUNC( FIELD_VOID, "CancelAtNextInterrupt", InputCancelAtNextInterrupt ), DEFINE_INPUTFUNC( FIELD_FLOAT, "PitchShift", InputPitchShiftPlayback ), DEFINE_INPUTFUNC( FIELD_STRING, "InterjectResponse", InputInterjectResponse ), DEFINE_INPUTFUNC( FIELD_VOID, "StopWaitingForActor", InputStopWaitingForActor ), DEFINE_INPUTFUNC( FIELD_INTEGER, "Trigger", InputTriggerEvent ), DEFINE_KEYFIELD( m_iPlayerDeathBehavior, FIELD_INTEGER, "onplayerdeath" ), DEFINE_INPUTFUNC( FIELD_VOID, "ScriptPlayerDeath", InputScriptPlayerDeath ), // Outputs DEFINE_OUTPUT( m_OnStart, "OnStart"), DEFINE_OUTPUT( m_OnCompletion, "OnCompletion"), DEFINE_OUTPUT( m_OnCanceled, "OnCanceled"), DEFINE_OUTPUT( m_OnTrigger1, "OnTrigger1"), DEFINE_OUTPUT( m_OnTrigger2, "OnTrigger2"), DEFINE_OUTPUT( m_OnTrigger3, "OnTrigger3"), DEFINE_OUTPUT( m_OnTrigger4, "OnTrigger4"), DEFINE_OUTPUT( m_OnTrigger5, "OnTrigger5"), DEFINE_OUTPUT( m_OnTrigger6, "OnTrigger6"), DEFINE_OUTPUT( m_OnTrigger7, "OnTrigger7"), DEFINE_OUTPUT( m_OnTrigger8, "OnTrigger8"), DEFINE_OUTPUT( m_OnTrigger9, "OnTrigger9"), DEFINE_OUTPUT( m_OnTrigger10, "OnTrigger10"), DEFINE_OUTPUT( m_OnTrigger11, "OnTrigger11"), DEFINE_OUTPUT( m_OnTrigger12, "OnTrigger12"), DEFINE_OUTPUT( m_OnTrigger13, "OnTrigger13"), DEFINE_OUTPUT( m_OnTrigger14, "OnTrigger14"), DEFINE_OUTPUT( m_OnTrigger15, "OnTrigger15"), DEFINE_OUTPUT( m_OnTrigger16, "OnTrigger16"), END_DATADESC() BEGIN_ENT_SCRIPTDESC( CSceneEntity, CBaseEntity, "Choreographed scene which controls animation and/or dialog on one or more actors." ) DEFINE_SCRIPTFUNC( EstimateLength, "Returns length of this scene in seconds." ) DEFINE_SCRIPTFUNC( IsPlayingBack, "If this scene is currently playing." ) DEFINE_SCRIPTFUNC( IsPaused, "If this scene is currently paused." ) DEFINE_SCRIPTFUNC( AddBroadcastTeamTarget, "Adds a team (by index) to the broadcast list" ) DEFINE_SCRIPTFUNC( RemoveBroadcastTeamTarget, "Removes a team (by index) from the broadcast list" ) DEFINE_SCRIPTFUNC_NAMED( ScriptFindNamedEntity, "FindNamedEntity", "given an entity reference, such as !target, get actual entity from scene object" ) DEFINE_SCRIPTFUNC_NAMED( ScriptLoadSceneFromString, "LoadSceneFromString", "given a dummy scene name and a vcd string, load the scene" ) END_SCRIPTDESC(); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CSceneEntity::CSceneEntity( void ) { m_bWaitingForActor = false; m_bWaitingForInterrupt = false; m_bInterruptedActorsScenes = false; m_bIsPlayingBack = false; m_bPaused = false; m_bMultiplayer = false; m_fPitch = 1.0f; m_iszSceneFile = NULL_STRING; m_iszResumeSceneFile = NULL_STRING; m_hWaitingForThisResumeScene = NULL; m_bWaitingForResumeScene = false; SetCurrentTime( 0.0f, false ); m_bCancelAtNextInterrupt = false; m_bAutomated = false; m_nAutomatedAction = SCENE_ACTION_UNKNOWN; m_flAutomationDelay = 0.0f; m_flAutomationTime = 0.0f; m_bPausedViaInput = false; ClearInterrupt(); m_pScene = NULL; m_bCompletedEarly = false; m_BusyActor = SCENE_BUSYACTOR_DEFAULT; // FindVar is a very slow operation, too slow even to tolerate in a // constructor. We do it once and only once, and store the result in // static space. if (!m_pcvSndMixahead) { m_pcvSndMixahead = cvar->FindVar( "snd_mixahead" ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CSceneEntity::~CSceneEntity( void ) { delete m_pRecipientFilter; m_pRecipientFilter = NULL; } //----------------------------------------------------------------------------- // Purpose: Resets time such that the client version of the .vcd is also updated, if appropriate // Input : t - // forceClientSync - forces new timestamp down to client .dll via networking //----------------------------------------------------------------------------- void CSceneEntity::SetCurrentTime( float t, bool bForceClientSync ) { m_flCurrentTime = t; if ( gpGlobals->maxClients == 1 || bForceClientSync ) { m_flForceClientTime = t; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneEntity::UpdateOnRemove( void ) { UnloadScene(); BaseClass::UpdateOnRemove(); if ( GetSceneManager() ) { GetSceneManager()->RemoveSceneEntity( this ); } } //----------------------------------------------------------------------------- // Purpose: // Input : *actor - // *soundname - // Output : CChoreoScene //----------------------------------------------------------------------------- CChoreoScene *CSceneEntity::GenerateSceneForSound( CBaseFlex *pFlexActor, const char *soundname ) { float duration = CBaseEntity::GetSoundDuration( soundname, pFlexActor ? STRING( pFlexActor->GetModelName() ) : NULL ); if( duration <= 0.0f ) { Warning( "CSceneEntity::GenerateSceneForSound: Couldn't determine duration of %s\n", soundname ); return NULL; } CChoreoScene *scene = new CChoreoScene( this ); if ( !scene ) { Warning( "CSceneEntity::GenerateSceneForSound: Failed to allocated new scene!!!\n" ); } else { scene->SetPrintFunc( LocalScene_Printf ); CChoreoActor *actor = scene->AllocActor(); CChoreoChannel *channel = scene->AllocChannel(); CChoreoEvent *event = scene->AllocEvent(); Assert( actor ); Assert( channel ); Assert( event ); if ( !actor || !channel || !event ) { Warning( "CSceneEntity::GenerateSceneForSound: Alloc of actor, channel, or event failed!!!\n" ); delete scene; return NULL; } // Set us up the actorz actor->SetName( "!self" ); // Could be pFlexActor->GetName()? actor->SetActive( true ); // Set us up the channelz channel->SetName( STRING( m_iszSceneFile ) ); channel->SetActor( actor ); // Add to actor actor->AddChannel( channel ); // Set us up the eventz event->SetType( CChoreoEvent::SPEAK ); event->SetName( soundname ); event->SetParameters( soundname ); event->SetStartTime( 0.0f ); event->SetUsingRelativeTag( false ); event->SetEndTime( duration ); event->SnapTimes(); // Add to channel channel->AddEvent( event ); // Point back to our owners event->SetChannel( channel ); event->SetActor( actor ); } return scene; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneEntity::Activate() { if ( m_bGenerated && !m_pScene ) { m_pScene = GenerateSceneForSound( m_hActor, STRING( m_iszSoundName ) ); } BaseClass::Activate(); if ( GetSceneManager() ) { GetSceneManager()->AddSceneEntity( this ); } } //----------------------------------------------------------------------------- // Purpose: // Output : float //----------------------------------------------------------------------------- float CSceneEntity::GetSoundSystemLatency( void ) { if ( m_pcvSndMixahead ) { return m_pcvSndMixahead->GetFloat(); } // Assume 100 msec sound system latency return SOUND_SYSTEM_LATENCY_DEFAULT; } //----------------------------------------------------------------------------- // Purpose: // Input : *scene - //----------------------------------------------------------------------------- void CSceneEntity::PrecacheScene( CChoreoScene *scene ) { Assert( scene ); // Iterate events and precache necessary resources for ( int i = 0; i < scene->GetNumEvents(); i++ ) { CChoreoEvent *event = scene->GetEvent( i ); if ( !event ) continue; // load any necessary data switch (event->GetType() ) { default: break; case CChoreoEvent::SPEAK: { // Defined in SoundEmitterSystem.cpp // NOTE: The script entries associated with .vcds are forced to preload to avoid // loading hitches during triggering PrecacheScriptSound( event->GetParameters() ); if ( event->GetCloseCaptionType() == CChoreoEvent::CC_MASTER && event->GetNumSlaves() > 0 ) { char tok[ CChoreoEvent::MAX_CCTOKEN_STRING ]; if ( event->GetPlaybackCloseCaptionToken( tok, sizeof( tok ) ) ) { PrecacheScriptSound( tok ); } } } break; case CChoreoEvent::SUBSCENE: { // Only allow a single level of subscenes for now if ( !scene->IsSubScene() ) { CChoreoScene *subscene = event->GetSubScene(); if ( !subscene ) { subscene = LoadScene( event->GetParameters(), this ); subscene->SetSubScene( true ); event->SetSubScene( subscene ); // Now precache it's resources, if any PrecacheScene( subscene ); } } } break; } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneEntity::Precache( void ) { if ( m_bGenerated ) return; if ( m_iszSceneFile == NULL_STRING ) return; if ( m_iszResumeSceneFile != NULL_STRING ) { PrecacheInstancedScene( STRING( m_iszResumeSceneFile ) ); } PrecacheInstancedScene( STRING( m_iszSceneFile ) ); } //----------------------------------------------------------------------------- // Purpose: // Input : *pActor - // *soundname - //----------------------------------------------------------------------------- void CSceneEntity::GenerateSoundScene( CBaseFlex *pActor, const char *soundname ) { m_bGenerated = true; m_iszSoundName = MAKE_STRING( soundname ); m_hActor = pActor; } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CSceneEntity::HasUnplayedSpeech( void ) { if ( m_pScene ) return m_pScene->HasUnplayedSpeech(); return false; } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CSceneEntity::HasFlexAnimation( void ) { if ( m_pScene ) return m_pScene->HasFlexAnimation(); return false; } //----------------------------------------------------------------------------- // Purpose: // Output : //----------------------------------------------------------------------------- void CSceneEntity::SetBackground( bool bIsBackground ) { if ( m_pScene ) { m_pScene->SetBackground( bIsBackground ); } } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CSceneEntity::IsBackground( void ) { if ( m_pScene ) return m_pScene->IsBackground( ); return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneEntity::OnRestore() { BaseClass::OnRestore(); // Fix saved games that have their pitch set to zero if ( m_fPitch < SCENE_MIN_PITCH || m_fPitch > SCENE_MAX_PITCH ) m_fPitch = 1.0f; if ( !m_bIsPlayingBack ) return; if ( !m_pScene ) { m_pScene = LoadScene( STRING( m_iszSceneFile ), this ); if ( !m_pScene ) { m_bSceneMissing = true; return; } OnLoaded(); if ( ShouldNetwork() ) { m_nSceneStringIndex = g_pStringTableClientSideChoreoScenes->AddString( CBaseEntity::IsServer(), STRING( m_iszSceneFile ) ); } UpdateTransmitState(); } m_bSceneMissing = false; int i; for ( i = 0 ; i < m_pScene->GetNumActors(); i++ ) { CBaseFlex *pTestActor = FindNamedActor( i ); if ( !pTestActor ) continue; if ( !pTestActor->MyCombatCharacterPointer() ) continue; // Needed? //if ( !pTestActor->MyCombatCharacterPointer()->IsAlive() ) // return; pTestActor->StartChoreoScene( m_pScene ); } float dt = SCENE_THINK_INTERVAL; bool paused = m_bPaused; m_bPaused = false; // roll back slightly so that pause events still trigger m_pScene->ResetSimulation( true, m_flCurrentTime - SCENE_THINK_INTERVAL, m_flCurrentTime ); m_pScene->SetTime( m_flCurrentTime - SCENE_THINK_INTERVAL ); SetCurrentTime( m_flCurrentTime, true ); // Robin: This causes a miscount of any interrupt events in the scene. // All the variables are saved/restored properly, so we can safely leave them alone. //ClearInterrupt(); SetRestoring( true ); DoThink( dt ); SetRestoring( false ); if ( paused ) { PausePlayback(); } NetworkProp()->NetworkStateForceUpdate(); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CSceneEntity::SetRestoring( bool bRestoring ) { m_bRestoring = bRestoring; if ( m_pScene ) { m_pScene->SetRestoring( bRestoring ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneEntity::Spawn( void ) { Precache(); } void CSceneEntity::PauseThink( void ) { if ( !m_pScene ) return; // Stay paused if pause occurred from interrupt if ( m_bInterrupted ) return; // If entity I/O paused the scene, then it'll have to resume/cancel the scene... if ( m_bPausedViaInput ) { // If we're waiting for a resume scene to finish, continue when it's done if ( m_bWaitingForResumeScene && !m_hWaitingForThisResumeScene ) { // Resume scene has finished, stop waiting for it m_bWaitingForResumeScene = false; } else { return; } } if ( !m_bAutomated ) { // FIXME: Game code should check for AI waiting conditions being met, etc. // // // bool bAllFinished = m_pScene->CheckEventCompletion( ); if ( bAllFinished ) { // Perform action switch ( m_nAutomatedAction ) { case SCENE_ACTION_RESUME: ResumePlayback(); break; case SCENE_ACTION_CANCEL: LocalScene_Printf( "%s : PauseThink canceling playback\n", STRING( m_iszSceneFile ) ); CancelPlayback(); break; default: ResumePlayback(); break; } // Reset m_bAutomated = false; m_nAutomatedAction = SCENE_ACTION_UNKNOWN; m_flAutomationTime = 0.0f; m_flAutomationDelay = 0.0f; m_bPausedViaInput = false; } return; } // Otherwise, see if resume/cancel is automatic and act accordingly if enough time // has passed m_flAutomationTime += (gpGlobals->frametime); if ( m_flAutomationDelay > 0.0f && m_flAutomationTime < m_flAutomationDelay ) return; // Perform action switch ( m_nAutomatedAction ) { case SCENE_ACTION_RESUME: LocalScene_Printf( "%s : Automatically resuming playback\n", STRING( m_iszSceneFile ) ); ResumePlayback(); break; case SCENE_ACTION_CANCEL: LocalScene_Printf( "%s : Automatically canceling playback\n", STRING( m_iszSceneFile ) ); CancelPlayback(); break; default: LocalScene_Printf( "%s : Unknown action %i, automatically resuming playback\n", STRING( m_iszSceneFile ), m_nAutomatedAction ); ResumePlayback(); break; } // Reset m_bAutomated = false; m_nAutomatedAction = SCENE_ACTION_UNKNOWN; m_flAutomationTime = 0.0f; m_flAutomationDelay = 0.0f; m_bPausedViaInput = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneEntity::DispatchPauseScene( CChoreoScene *scene, const char *parameters ) { // Don't pause during restore, since we'll be restoring the pause state already if ( m_bRestoring ) return; // FIXME: Hook this up to AI, etc. somehow, perhaps poll each actor for conditions using // scene resume condition iterator PausePlayback(); char token[1024]; m_bPausedViaInput = false; m_bAutomated = false; m_nAutomatedAction = SCENE_ACTION_UNKNOWN; m_flAutomationDelay = 0.0f; m_flAutomationTime = 0.0f; // Check for auto resume/cancel const char *buffer = parameters; buffer = engine->ParseFile( buffer, token, sizeof( token ) ); if ( !stricmp( token, "automate" ) ) { buffer = engine->ParseFile( buffer, token, sizeof( token ) ); if ( !stricmp( token, "Cancel" ) ) { m_nAutomatedAction = SCENE_ACTION_CANCEL; } else if ( !stricmp( token, "Resume" ) ) { m_nAutomatedAction = SCENE_ACTION_RESUME; } if ( m_nAutomatedAction != SCENE_ACTION_UNKNOWN ) { buffer = engine->ParseFile( buffer, token, sizeof( token ) ); m_flAutomationDelay = (float)atof( token ); if ( m_flAutomationDelay > 0.0f ) { // Success m_bAutomated = true; m_flAutomationTime = 0.0f; } } } } //----------------------------------------------------------------------------- // Purpose: // Input : *scene - // *event - //----------------------------------------------------------------------------- void CSceneEntity::DispatchProcessLoop( CChoreoScene *scene, CChoreoEvent *event ) { // Don't restore this event since it's implied in the current "state" of the scene timer, etc. if ( m_bRestoring ) return; Assert( scene ); Assert( event->GetType() == CChoreoEvent::LOOP ); float backtime = (float)atof( event->GetParameters() ); bool process = true; int counter = event->GetLoopCount(); if ( counter != -1 ) { int remaining = event->GetNumLoopsRemaining(); if ( remaining <= 0 ) { process = false; } else { event->SetNumLoopsRemaining( --remaining ); } } if ( !process ) return; scene->LoopToTime( backtime ); SetCurrentTime( backtime, true ); } //----------------------------------------------------------------------------- // Purpose: Flag the scene as already "completed" // Input : *scene - // *parameters - //----------------------------------------------------------------------------- void CSceneEntity::DispatchStopPoint( CChoreoScene *scene, const char *parameters ) { if ( m_bCompletedEarly ) { Assert( 0 ); Warning( "Scene '%s' with two stop point events!\n", STRING( m_iszSceneFile ) ); return; } // Fire completion trigger early m_bCompletedEarly = true; m_OnCompletion.FireOutput( this, this, 0 ); } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CSceneEntity::IsInterruptable() { return ( m_nInterruptCount > 0 ) ? true : false; } //----------------------------------------------------------------------------- // Purpose: // Input : *scene - // *actor - // *event - //----------------------------------------------------------------------------- void CSceneEntity::DispatchStartInterrupt( CChoreoScene *scene, CChoreoEvent *event ) { // Don't re-interrupt during restore if ( m_bRestoring ) return; // If we're supposed to cancel at our next interrupt point, cancel now if ( m_bCancelAtNextInterrupt ) { m_bCancelAtNextInterrupt = false; LocalScene_Printf( "%s : cancelled via interrupt\n", STRING( m_iszSceneFile ) ); CancelPlayback(); return; } ++m_nInterruptCount; } //----------------------------------------------------------------------------- // Purpose: // Input : *scene - // *actor - // *event - //----------------------------------------------------------------------------- void CSceneEntity::DispatchEndInterrupt( CChoreoScene *scene, CChoreoEvent *event ) { // Don't re-interrupt during restore if ( m_bRestoring ) return; --m_nInterruptCount; if ( m_nInterruptCount < 0 ) { m_nInterruptCount = 0; } } //----------------------------------------------------------------------------- // Purpose: // Input : *actor - // *event - //----------------------------------------------------------------------------- void CSceneEntity::DispatchStartExpression( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { actor->AddSceneEvent( scene, event, NULL, this ); } //----------------------------------------------------------------------------- // Purpose: // Input : *actor - // *event - //----------------------------------------------------------------------------- void CSceneEntity::DispatchEndExpression( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { actor->RemoveSceneEvent( scene, event, false ); } //----------------------------------------------------------------------------- // Purpose: // Input : *actor - // *event - //----------------------------------------------------------------------------- void CSceneEntity::DispatchStartFlexAnimation( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { actor->AddSceneEvent( scene, event, NULL, this ); } //----------------------------------------------------------------------------- // Purpose: // Input : *actor - // *event - //----------------------------------------------------------------------------- void CSceneEntity::DispatchEndFlexAnimation( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { actor->RemoveSceneEvent( scene, event, false ); } //----------------------------------------------------------------------------- // Purpose: // Input : *actor - // *parameters - //----------------------------------------------------------------------------- void CSceneEntity::DispatchStartGesture( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { // Ingore null gestures if ( !Q_stricmp( event->GetName(), "NULL" ) ) return; actor->AddSceneEvent( scene, event, NULL, this ); } //----------------------------------------------------------------------------- // Purpose: // Input : *actor - // *parameters - //----------------------------------------------------------------------------- void CSceneEntity::DispatchEndGesture( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { // Ingore null gestures if ( !Q_stricmp( event->GetName(), "NULL" ) ) return; actor->RemoveSceneEvent( scene, event, m_bRestoring ); } //----------------------------------------------------------------------------- // Purpose: // Input : *actor - // *parameters - //----------------------------------------------------------------------------- void CSceneEntity::DispatchStartGeneric( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { CBaseEntity *pTarget = FindNamedEntity( event->GetParameters2( ) ); actor->AddSceneEvent( scene, event, pTarget, this ); } //----------------------------------------------------------------------------- // Purpose: // Input : *actor - // *parameters - //----------------------------------------------------------------------------- void CSceneEntity::DispatchEndGeneric( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { actor->RemoveSceneEvent( scene, event, m_bRestoring ); } //----------------------------------------------------------------------------- // Purpose: // Input : *actor - // *actor2 - //----------------------------------------------------------------------------- void CSceneEntity::DispatchStartLookAt( CChoreoScene *scene, CBaseFlex *actor, CBaseEntity *actor2, CChoreoEvent *event ) { actor->AddSceneEvent( scene, event, actor2, this ); } void CSceneEntity::DispatchEndLookAt( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { actor->RemoveSceneEvent( scene, event, m_bRestoring ); } //----------------------------------------------------------------------------- // Purpose: Move to spot/actor // FIXME: Need to allow this to take arbitrary amount of time and pause playback // while waiting for actor to move into position // Input : *actor - // *parameters - //----------------------------------------------------------------------------- void CSceneEntity::DispatchStartMoveTo( CChoreoScene *scene, CBaseFlex *actor, CBaseEntity *actor2, CChoreoEvent *event ) { actor->AddSceneEvent( scene, event, actor2, this ); } void CSceneEntity::DispatchEndMoveTo( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { actor->RemoveSceneEvent( scene, event, m_bRestoring ); } //----------------------------------------------------------------------------- // Purpose: // Input : *token - // listener - // soundorigins - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool AttenuateCaption( const char *token, const Vector& listener, CUtlVector< Vector >& soundorigins ) { if ( scene_maxcaptionradius.GetFloat() <= 0.0f ) { return false; } int c = soundorigins.Count(); if ( c <= 0 ) { return false; } float maxdistSqr = scene_maxcaptionradius.GetFloat() * scene_maxcaptionradius.GetFloat(); for ( int i = 0; i < c; ++i ) { const Vector& org = soundorigins[ i ]; float distSqr = ( org - listener ).LengthSqr(); if ( distSqr <= maxdistSqr ) { return false; } } // All sound sources too far, don't show caption... return true; } //----------------------------------------------------------------------------- // Purpose: // Input : *event - which event // player - which recipient // buf, buflen: where to put the data // Output : Returns true if the sound should be played/prefetched //----------------------------------------------------------------------------- bool CSceneEntity::GetSoundNameForPlayer( CChoreoEvent *event, CBasePlayer *player, char *buf, size_t buflen ) { Assert( event ); Assert( player ); Assert( buf ); Assert( buflen > 0 ); bool ismasterevent = true; char tok[ CChoreoEvent::MAX_CCTOKEN_STRING ]; bool validtoken = false; tok[ 0 ] = 0; if ( event->GetCloseCaptionType() == CChoreoEvent::CC_SLAVE || event->GetCloseCaptionType() == CChoreoEvent::CC_DISABLED ) { ismasterevent = false; } else { validtoken = event->GetPlaybackCloseCaptionToken( tok, sizeof( tok ) ); } // For english users, assume we emit the sound normally Q_strncpy( buf, event->GetParameters(), buflen ); bool usingEnglish = true; if ( !IsXbox() ) { char const *cvarvalue = engine->GetClientConVarValue( player->entindex(), "english" ); if ( cvarvalue && *cvarvalue && Q_atoi( cvarvalue ) != 1 ) { usingEnglish = false; } } // This makes it like they are running in another language if ( scene_forcecombined.GetBool() ) { usingEnglish = false; } if ( usingEnglish ) { // English sounds always play return true; } if ( ismasterevent ) { // Master event sounds always play too (master will be the combined .wav) if ( validtoken ) { Q_strncpy( buf, tok, buflen ); } return true; } // Slave events don't play any sound... return false; } static CAI_BaseActor *ToBaseActor( CBaseFlex *actor ) { return dynamic_cast( actor ); } //----------------------------------------------------------------------------- // Purpose: Playback sound file that contains phonemes // Input : *actor - // *parameters - //----------------------------------------------------------------------------- void CSceneEntity::DispatchStartSpeak( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event, soundlevel_t iSoundlevel ) { // Emit sound if ( actor ) { CPASAttenuationFilter filter( actor, iSoundlevel ); if ( m_pRecipientFilter ) { int filterCount = filter.GetRecipientCount(); int recipientPlayerCount = m_pRecipientFilter->GetRecipientCount(); for ( int i = filterCount-1; i >= 0; --i ) { int playerindex = filter.GetRecipientIndex( i ); bool bFound = false; for ( int j = 0; j < recipientPlayerCount; ++j ) { if ( m_pRecipientFilter->GetRecipientIndex(j) == playerindex ) { bFound = true; break; } } if ( !bFound ) { filter.RemoveRecipientByPlayerIndex( playerindex ); } } } float time_in_past = m_flCurrentTime - event->GetStartTime() ; float soundtime = gpGlobals->curtime - time_in_past; if ( m_bRestoring ) { // Need to queue sounds on restore because the player has not yet connected GetSceneManager()->QueueRestoredSound( actor, event->GetParameters(), iSoundlevel, time_in_past ); return; } // Add padding to prevent any other talker talking right after I'm done, because I might // be continuing speaking with another scene. float flDuration = event->GetDuration() - time_in_past; CAI_BaseActor *pBaseActor = ToBaseActor( actor ); if ( pBaseActor ) { pBaseActor->NoteSpeaking( flDuration, GetPostSpeakDelay() ); } else if ( actor->IsNPC() && actor->MyNPCPointer() ) { GetSpeechSemaphore( actor->MyNPCPointer() )->Acquire( flDuration + GetPostSpeakDelay(), actor ); } EmitSound_t es; es.m_nChannel = CHAN_VOICE; es.m_flVolume = 1; es.m_SoundLevel = iSoundlevel; // Only specify exact delay in single player es.m_flSoundTime = ( gpGlobals->maxClients == 1 ) ? soundtime : 0.0f; if ( scene->ShouldIgnorePhonemes() ) { es.m_nFlags |= SND_IGNORE_PHONEMES; } // No CC since we do it manually // FIXME: This will change es.m_bEmitCloseCaption = false; // Captions only route to host player (there is only one closecaptioning HUD) filter.RemoveSplitScreenPlayers(); int c = filter.GetRecipientCount(); for ( int i = 0; i < c; ++i ) { int playerindex = filter.GetRecipientIndex( i ); CBasePlayer *player = UTIL_PlayerByIndex( playerindex ); if ( !player ) continue; CSingleUserRecipientFilter filter2( player ); char soundname[ 512 ]; if ( !GetSoundNameForPlayer( event, player, soundname, sizeof( soundname ) ) ) { continue; } es.m_pSoundName = soundname; // keep track of the last few sounds played for bug reports speechListSounds[ speechListIndex ].time = gpGlobals->curtime; Q_strncpy( speechListSounds[ speechListIndex ].name, soundname, sizeof( speechListSounds[ 0 ].name ) ); Q_strncpy( speechListSounds[ speechListIndex ].sceneName, ( scene ) ? scene->GetFilename() : "", sizeof( speechListSounds[ 0 ].sceneName ) ); speechListIndex++; if ( speechListIndex >= SPEECH_LIST_MAX_SOUNDS ) { speechListIndex = 0; } // Warning( "Speak %s\n", soundname ); if ( m_fPitch != 1.0f ) { if ( es.m_nPitch ) es.m_nPitch = static_cast( es.m_nPitch ) * m_fPitch; else es.m_nPitch = 100.0f * m_fPitch; es.m_nFlags |= SND_CHANGE_PITCH; } EmitSound( filter2, actor->entindex(), es ); actor->AddSceneEvent( scene, event, NULL, this ); } // Close captioning only on master token no matter what... if ( event->GetCloseCaptionType() == CChoreoEvent::CC_MASTER ) { char tok[ CChoreoEvent::MAX_CCTOKEN_STRING ]; bool validtoken = event->GetPlaybackCloseCaptionToken( tok, sizeof( tok ) ); if ( validtoken ) { char lowercase[ 256 ]; Q_strncpy( lowercase, tok, sizeof( lowercase ) ); Q_strlower( lowercase ); // Remove any players who don't want close captions CBaseEntity::RemoveRecipientsIfNotCloseCaptioning( filter ); // Certain events are marked "don't attenuate", (breencast), skip those here if ( !event->IsSuppressingCaptionAttenuation() && ( filter.GetRecipientCount() > 0 ) ) { int c = filter.GetRecipientCount(); for ( int i = c - 1 ; i >= 0; --i ) { CBasePlayer *player = UTIL_PlayerByIndex( filter.GetRecipientIndex( i ) ); if ( !player ) continue; Vector playerOrigin = player->GetAbsOrigin(); if ( iSoundlevel != SNDLVL_NONE && AttenuateCaption( lowercase, playerOrigin, es.m_UtlVecSoundOrigin ) ) { // If the player has a view entity, measure the distance to that if ( !player->GetViewEntity() || AttenuateCaption( lowercase, player->GetViewEntity()->GetAbsOrigin(), es.m_UtlVecSoundOrigin ) ) { filter.RemoveRecipient( player ); } } } } // Anyone left? if ( filter.GetRecipientCount() > 0 ) { float endtime = event->GetLastSlaveEndTime(); float durationShort = event->GetDuration(); float durationLong = endtime - event->GetStartTime(); float duration = MAX( durationShort, durationLong ); char const *pszActorModel = STRING( actor->GetModelName() ); gender_t gender = soundemitterbase->GetActorGender( pszActorModel ); char lowercase_nogender[ 256 ]; Q_strncpy( lowercase_nogender, lowercase, sizeof( lowercase_nogender ) ); bool bTriedGender = false; if ( gender == GENDER_MALE ) { Q_strncat( lowercase, "_male", sizeof( lowercase ), COPY_ALL_CHARACTERS ); bTriedGender = true; } else if ( gender == GENDER_FEMALE ) { Q_strncat( lowercase, "_female", sizeof( lowercase ), COPY_ALL_CHARACTERS ); bTriedGender = true; } unsigned int hash = 0u; bool bFound = GetCaptionHash( lowercase, true, hash ); // if not found, try the no-gender version if ( !bFound && bTriedGender ) { bFound = GetCaptionHash( lowercase_nogender, true, hash ); } if ( bFound ) { if ( CanEmitCaption( hash ) ) { // Send caption and duration hint down to client UserMessageBegin( filter, "CloseCaption" ); WRITE_LONG( hash ); WRITE_UBITLONG( clamp( (int)( duration * 10.0f ), 0, 65535 ), 15 ), WRITE_UBITLONG( 0, 1 ), MessageEnd(); } } } } } } } void CSceneEntity::DispatchEndSpeak( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { actor->RemoveSceneEvent( scene, event, m_bRestoring ); } //----------------------------------------------------------------------------- // Purpose: // Input : *actor - // *actor2 - //----------------------------------------------------------------------------- void CSceneEntity::DispatchStartFace( CChoreoScene *scene, CBaseFlex *actor, CBaseEntity *actor2, CChoreoEvent *event ) { actor->AddSceneEvent( scene, event, actor2, this ); } //----------------------------------------------------------------------------- // Purpose: // Input : *actor - // *actor2 - //----------------------------------------------------------------------------- void CSceneEntity::DispatchEndFace( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { actor->RemoveSceneEvent( scene, event, m_bRestoring ); } //----------------------------------------------------------------------------- // Purpose: // Input : *actor - //----------------------------------------------------------------------------- void CSceneEntity::DispatchStartSequence( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { actor->AddSceneEvent( scene, event, NULL, this ); } //----------------------------------------------------------------------------- // Purpose: // Input : *actor - //----------------------------------------------------------------------------- void CSceneEntity::DispatchEndSequence( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { actor->RemoveSceneEvent( scene, event, m_bRestoring ); } //----------------------------------------------------------------------------- // Purpose: NPC can play interstitial vcds (such as responding to the player doing something during a scene) // Input : *scene - // *actor - // *event - //----------------------------------------------------------------------------- void CSceneEntity::DispatchStartPermitResponses( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { actor->SetPermitResponse( gpGlobals->curtime + event->GetDuration() ); } //----------------------------------------------------------------------------- // Purpose: // Input : *scene - // *actor - // *event - //----------------------------------------------------------------------------- void CSceneEntity::DispatchEndPermitResponses( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { actor->SetPermitResponse( 0 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CSceneEntity::EstimateLength( void ) { if ( !m_pScene ) { return GetSceneDuration( STRING( m_iszSceneFile ) ); } return m_pScene->FindStopTime(); } //----------------------------------------------------------------------------- // Purpose: // NOTE: returns false if scene hasn't loaded yet //----------------------------------------------------------------------------- void CSceneEntity::CancelIfSceneInvolvesActor( CBaseEntity *pActor ) { if ( InvolvesActor( pActor ) ) { LocalScene_Printf( "%s : cancelled for '%s'\n", STRING( m_iszSceneFile ), pActor->GetDebugName() ); CancelPlayback(); } } //----------------------------------------------------------------------------- // Purpose: // NOTE: returns false if scene hasn't loaded yet //----------------------------------------------------------------------------- bool CSceneEntity::InvolvesActor( CBaseEntity *pActor ) { if ( !m_pScene ) return false; int i; for ( i = 0 ; i < m_pScene->GetNumActors(); i++ ) { CBaseFlex *pTestActor = FindNamedActor( i ); if ( !pTestActor ) continue; if ( pTestActor == pActor ) return true; } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneEntity::DoThink( float frametime ) { CheckInterruptCompletion(); if ( m_bWaitingForActor || m_bWaitingForInterrupt ) { // Try to start playback. StartPlayback(); } if ( !m_pScene ) return; if ( !m_bIsPlayingBack ) return; // catch bad pitch shifting from old save games Assert( m_fPitch >= SCENE_MIN_PITCH && m_fPitch <= SCENE_MAX_PITCH ); m_fPitch = clamp( m_fPitch, SCENE_MIN_PITCH, SCENE_MAX_PITCH ); if ( m_bPaused ) { PauseThink(); return; } // Msg("%.2f %s\n", gpGlobals->curtime, STRING( m_iszSceneFile ) ); //Msg( "SV: %d, %f for %s\n", gpGlobals->tickcount, m_flCurrentTime, m_pScene->GetFilename() ); m_flFrameTime = frametime; m_pScene->SetSoundFileStartupLatency( GetSoundSystemLatency() ); // Tell scene to go m_pScene->Think( m_flCurrentTime ); // Did we get to the end if ( !m_bPaused ) { // Drive simulation time for scene SetCurrentTime( m_flCurrentTime + m_flFrameTime * m_fPitch, false ); if ( m_pScene->SimulationFinished() ) { OnSceneFinished( false, true ); // Stop them from doing anything special ClearSchedules( m_pScene ); } } else { // Drive simulation time for scene SetCurrentTime( m_pScene->GetTime(), true ); } } //----------------------------------------------------------------------------- // Purpose: Input handlers //----------------------------------------------------------------------------- void CSceneEntity::InputStartPlayback( inputdata_t &inputdata ) { // Already playing, ignore if ( m_bIsPlayingBack ) return; // Already waiting on someone. if ( m_bWaitingForActor || m_bWaitingForInterrupt ) return; ClearActivatorTargets(); m_hActivator = inputdata.pActivator; StartPlayback(); } void CSceneEntity::InputPausePlayback( inputdata_t &inputdata ) { PausePlayback(); m_bPausedViaInput = true; } void CSceneEntity::InputResumePlayback( inputdata_t &inputdata ) { ResumePlayback(); } void CSceneEntity::InputCancelPlayback( inputdata_t &inputdata ) { LocalScene_Printf( "%s : cancelled via input\n", STRING( m_iszSceneFile ) ); CancelPlayback(); } void CSceneEntity::InputScriptPlayerDeath( inputdata_t &inputdata ) { if ( m_iPlayerDeathBehavior == SCRIPT_CANCEL ) { LocalScene_Printf( "%s : cancelled via player death\n", STRING( m_iszSceneFile ) ); CancelPlayback(); } } void CSceneEntity::InputCancelAtNextInterrupt( inputdata_t &inputdata ) { // If we're currently in an interruptable point, interrupt immediately if ( IsInterruptable() ) { LocalScene_Printf( "%s : cancelled via input at interrupt point\n", STRING( m_iszSceneFile ) ); CancelPlayback(); return; } // Otherwise, cancel when we next hit an interrupt point m_bCancelAtNextInterrupt = true; } void CSceneEntity::InputPitchShiftPlayback( inputdata_t &inputdata ) { PitchShiftPlayback( inputdata.value.Float() ); } void CSceneEntity::InputTriggerEvent( inputdata_t &inputdata ) { CBaseEntity *pActivator = this; // at some point, find this from the inputdata switch ( inputdata.value.Int() ) { case 1: m_OnTrigger1.FireOutput( pActivator, this, 0 ); break; case 2: m_OnTrigger2.FireOutput( pActivator, this, 0 ); break; case 3: m_OnTrigger3.FireOutput( pActivator, this, 0 ); break; case 4: m_OnTrigger4.FireOutput( pActivator, this, 0 ); break; case 5: m_OnTrigger5.FireOutput( pActivator, this, 0 ); break; case 6: m_OnTrigger6.FireOutput( pActivator, this, 0 ); break; case 7: m_OnTrigger7.FireOutput( pActivator, this, 0 ); break; case 8: m_OnTrigger8.FireOutput( pActivator, this, 0 ); break; case 9: m_OnTrigger9.FireOutput( pActivator, this, 0 ); break; case 10: m_OnTrigger10.FireOutput( pActivator, this, 0 ); break; case 11: m_OnTrigger11.FireOutput( pActivator, this, 0 ); break; case 12: m_OnTrigger12.FireOutput( pActivator, this, 0 ); break; case 13: m_OnTrigger13.FireOutput( pActivator, this, 0 ); break; case 14: m_OnTrigger14.FireOutput( pActivator, this, 0 ); break; case 15: m_OnTrigger15.FireOutput( pActivator, this, 0 ); break; case 16: m_OnTrigger16.FireOutput( pActivator, this, 0 ); break; } } struct NPCInterjection { AI_Response response; CAI_BaseActor *npc; }; //----------------------------------------------------------------------------- // Purpose: // Input : &inputdata - //----------------------------------------------------------------------------- void CSceneEntity::InputInterjectResponse( inputdata_t &inputdata ) { // Not currently playing a scene if ( !m_pScene ) { return; } CUtlVector< CAI_BaseActor * > candidates; int i; for ( i = 0 ; i < m_pScene->GetNumActors(); i++ ) { CBaseFlex *pTestActor = FindNamedActor( i ); if ( !pTestActor ) continue; CAI_BaseActor *pBaseActor = ToBaseActor( pTestActor ); if ( !pBaseActor ) continue; if ( !pBaseActor->IsAlive() ) continue; candidates.AddToTail( pBaseActor ); } int c = candidates.Count(); if ( !c ) { return; } int useIndex = 0; if ( !m_bIsPlayingBack ) { // Use any actor if not playing a scene useIndex = RandomInt( 0, c - 1 ); } else { CUtlVector< NPCInterjection > validResponses; char modifiers[ 512 ]; Q_snprintf( modifiers, sizeof( modifiers ), "scene:%s", STRING( GetEntityName() ) ); AIConcept_t concept(inputdata.value.String()); for ( int i = 0; i < c; i++ ) { CAI_BaseActor *npc = candidates[ i ]; Assert( npc ); AI_CriteriaSet set; npc->GatherCriteria( &set, concept, modifiers ); AI_Response response; if ( !npc->FindResponse( response, concept, &set ) ) continue; float duration = npc->GetResponseDuration( &response ); // Couldn't look it up if ( duration <= 0.0f ) continue; if ( !npc->PermitResponse( duration ) ) { continue; } // NPCInterjection inter; inter.response = response; inter.npc = npc; validResponses.AddToTail( inter ); } int rcount = validResponses.Count(); if ( rcount >= 1 ) { int slot = RandomInt( 0, rcount - 1 ); for ( int i = 0; i < rcount; i++ ) { NPCInterjection *pInterjection = &validResponses[ i ]; if ( i == slot ) { pInterjection->npc->SpeakDispatchResponse( inputdata.value.String(), &pInterjection->response, NULL ); } else { // delete pInterjection->response; } } } } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CSceneEntity::InputStopWaitingForActor( inputdata_t &inputdata ) { if( m_bIsPlayingBack ) { // Already started. return; } m_bWaitingForActor = false; } bool CSceneEntity::CheckActors() { Assert( m_pScene ); if ( !m_pScene ) return false; int i; for ( i = 0 ; i < m_pScene->GetNumActors(); i++ ) { CBaseFlex *pTestActor = FindNamedActor( i ); if ( !pTestActor ) continue; if ( !pTestActor->MyCombatCharacterPointer() ) continue; if ( !pTestActor->MyCombatCharacterPointer()->IsAlive() ) return false; if ( m_BusyActor == SCENE_BUSYACTOR_WAIT ) { CAI_BaseNPC *pActor = pTestActor->MyNPCPointer(); if ( pActor ) { bool bShouldWait = false; if ( hl2_episodic.GetBool() ) { // Episodic waits until the NPC is fully finished with any .vcd with speech in it if ( IsRunningScriptedSceneWithSpeech( pActor ) ) { bShouldWait = true; } #ifdef HL2_EPISODIC // HACK: Alyx cannot play scenes when she's in the middle of transitioning if ( pActor->IsInAVehicle() ) { CNPC_Alyx *pAlyx = dynamic_cast(pActor); if ( pAlyx != NULL && ( pAlyx->GetPassengerState() == PASSENGER_STATE_ENTERING || pAlyx->GetPassengerState() == PASSENGER_STATE_EXITING ) ) { bShouldWait = true; } } #endif // HL2_EPISODIC } if ( pActor->GetExpresser() && pActor->GetExpresser()->IsSpeaking() ) { bShouldWait = true; } if ( bShouldWait ) { // One of the actors for this scene is talking already. // Try again next think. m_bWaitingForActor = true; return false; } } } else if ( m_BusyActor == SCENE_BUSYACTOR_INTERRUPT || m_BusyActor == SCENE_BUSYACTOR_INTERRUPT_CANCEL ) { CBaseCombatCharacter *pActor = pTestActor->MyCombatCharacterPointer(); if ( pActor && !IsInInterruptableScenes( pActor ) ) { // One of the actors is in a scene that's not at an interrupt point. // Wait until the scene finishes or an interrupt point is reached. m_bWaitingForInterrupt = true; return false; } if ( m_BusyActor == SCENE_BUSYACTOR_INTERRUPT_CANCEL ) { // Cancel existing scenes RemoveActorFromScriptedScenes( pActor, false ); } else { // Pause existing scenes PauseActorsScriptedScenes( pActor, false ); m_bInterruptedActorsScenes = true; } } pTestActor->StartChoreoScene( m_pScene ); } return true; } static ConVar scene_async_prefetch_spew( "scene_async_prefetch_spew", "0", 0, "Display async .ani file loading info." ); void CSceneEntity::PrefetchAnimBlocks( CChoreoScene *scene ) { Assert( scene ); // Build a fast lookup, too CUtlMap< CChoreoActor *, CBaseFlex *> actorMap( 0, 0, DefLessFunc( CChoreoActor * ) ); int spew = scene_async_prefetch_spew.GetInt(); int resident = 0; int checked = 0; MDLCACHE_CRITICAL_SECTION(); // Iterate events and precache necessary resources for ( int i = 0; i < scene->GetNumEvents(); i++ ) { CChoreoEvent *event = scene->GetEvent( i ); if ( !event ) continue; // load any necessary data switch ( event->GetType() ) { default: break; case CChoreoEvent::SEQUENCE: case CChoreoEvent::GESTURE: { CChoreoActor *actor = event->GetActor(); if ( actor ) { CBaseFlex *pActor = NULL; int idx = actorMap.Find( actor ); if ( idx == actorMap.InvalidIndex() ) { pActor = FindNamedActor( actor ); idx = actorMap.Insert( actor, pActor ); } else { pActor = actorMap[ idx ]; } if ( pActor ) { int seq = pActor->LookupSequence( event->GetParameters() ); if ( seq >= 0 ) { CStudioHdr *pStudioHdr = pActor->GetModelPtr(); if ( pStudioHdr ) { // Now look up the animblock mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( seq ); for ( int i = 0 ; i < seqdesc.groupsize[ 0 ] ; ++i ) { for ( int j = 0; j < seqdesc.groupsize[ 1 ]; ++j ) { int animation = seqdesc.anim( i, j ); int baseanimation = pStudioHdr->iRelativeAnim( seq, animation ); mstudioanimdesc_t &animdesc = pStudioHdr->pAnimdesc( baseanimation ); ++checked; if ( spew != 0 ) { Msg( "%s checking block %d\n", pStudioHdr->pszName(), animdesc.animblock ); } // Async load the animation int iFrame = 0; const byte *panim = animdesc.pAnim( &iFrame ); if ( panim ) { ++resident; if ( spew > 1 ) { Msg( "%s:%s[%i:%i] was resident\n", pStudioHdr->pszName(), animdesc.pszName(), i, j ); } } else { if ( spew != 0 ) { Msg( "%s:%s[%i:%i] async load\n", pStudioHdr->pszName(), animdesc.pszName(), i, j ); } } } } } } } } } break; } } if ( !spew || checked <= 0 ) return; Msg( "%d of %d animations resident\n", resident, checked ); } void CSceneEntity::OnLoaded() { m_bMultiplayer = gpGlobals->maxClients > 1; } //----------------------------------------------------------------------------- // Purpose: Initiate scene playback //----------------------------------------------------------------------------- void CSceneEntity::StartPlayback( void ) { if ( !m_pScene ) { if ( m_bSceneMissing ) return; m_pScene = LoadScene( STRING( m_iszSceneFile ), this ); if ( !m_pScene ) { DevMsg( "%s missing from scenes.image\n", STRING( m_iszSceneFile ) ); m_bSceneMissing = true; return; } OnLoaded(); if ( ShouldNetwork() ) { m_nSceneStringIndex = g_pStringTableClientSideChoreoScenes->AddString( CBaseEntity::IsServer(), STRING( m_iszSceneFile ) ); } UpdateTransmitState(); } if ( m_bIsPlayingBack ) return; // Make sure actors are alive and able to handle this scene now, otherwise // we'll wait for them to show up if ( !CheckActors() ) { return; } m_bCompletedEarly = false; m_bWaitingForActor = false; m_bWaitingForInterrupt = false; m_bIsPlayingBack = true; NetworkProp()->NetworkStateForceUpdate(); m_bPaused = false; SetCurrentTime( 0.0f, true ); m_pScene->ResetSimulation(); ClearInterrupt(); // Put face back in neutral pose ClearSceneEvents( m_pScene, false ); m_OnStart.FireOutput( this, this, 0 ); // Aysnchronously load speak sounds CUtlSymbolTable prefetchSoundSymbolTable; CUtlRBTree< SpeakEventSound_t > soundnames( 0, 0, SpeakEventSoundLessFunc ); BuildSortedSpeakEventSoundsPrefetchList( m_pScene, prefetchSoundSymbolTable, soundnames, 0.0f ); PrefetchSpeakEventSounds( prefetchSoundSymbolTable, soundnames ); // Tell any managers we're within that we've started int c = m_hListManagers.Count(); for ( int i = 0; i < c; i++ ) { if ( m_hListManagers[i] ) { m_hListManagers[i]->SceneStarted( this ); } } PrefetchAnimBlocks( m_pScene ); } //----------------------------------------------------------------------------- // Purpose: Static method used to sort by event start time //----------------------------------------------------------------------------- bool CSceneEntity::SpeakEventSoundLessFunc( const SpeakEventSound_t& lhs, const SpeakEventSound_t& rhs ) { return lhs.m_flStartTime < rhs.m_flStartTime; } //----------------------------------------------------------------------------- // Purpose: Prefetches the list of sounds build by BuildSortedSpeakEventSoundsPrefetchList //----------------------------------------------------------------------------- void CSceneEntity::PrefetchSpeakEventSounds( CUtlSymbolTable& table, CUtlRBTree< SpeakEventSound_t >& soundnames ) { for ( int i = soundnames.FirstInorder(); i != soundnames.InvalidIndex() ; i = soundnames.NextInorder( i ) ) { SpeakEventSound_t& sound = soundnames[ i ]; // Look it up in the string table char const *soundname = table.String( sound.m_Symbol ); // Warning( "Prefetch %s\n", soundname ); PrefetchScriptSound( soundname ); } } //----------------------------------------------------------------------------- // Purpose: Builds list of sounds sorted by start time for prefetching //----------------------------------------------------------------------------- void CSceneEntity::BuildSortedSpeakEventSoundsPrefetchList( CChoreoScene *scene, CUtlSymbolTable& table, CUtlRBTree< SpeakEventSound_t >& soundnames, float timeOffset ) { Assert( scene ); // Iterate events and precache necessary resources for ( int i = 0; i < scene->GetNumEvents(); i++ ) { CChoreoEvent *event = scene->GetEvent( i ); if ( !event ) continue; // load any necessary data switch (event->GetType() ) { default: break; case CChoreoEvent::SPEAK: { // NOTE: The script entries associated with .vcds are forced to preload to avoid // loading hitches during triggering char soundname[ CChoreoEvent::MAX_CCTOKEN_STRING ]; Q_strncpy( soundname, event->GetParameters(), sizeof( soundname ) ); if ( event->GetCloseCaptionType() == CChoreoEvent::CC_MASTER ) { event->GetPlaybackCloseCaptionToken( soundname, sizeof( soundname ) ); } // In single player, try to use the combined or regular .wav files as needed if ( gpGlobals->maxClients == 1 ) { CBasePlayer *player = UTIL_GetLocalPlayer(); if ( player && !GetSoundNameForPlayer( event, player, soundname, sizeof( soundname ) ) ) { // Skip to next event continue; } } /* else { // UNDONE: Probably need some other solution in multiplayer... (not sure how to "prefetch" on certain players // with one sound, but not prefetch the same sound for others...) } */ SpeakEventSound_t ses; ses.m_Symbol = table.AddString( soundname ); ses.m_flStartTime = timeOffset + event->GetStartTime(); soundnames.Insert( ses ); } break; case CChoreoEvent::SUBSCENE: { // Only allow a single level of subscenes for now if ( !scene->IsSubScene() ) { CChoreoScene *subscene = event->GetSubScene(); if ( !subscene ) { subscene = LoadScene( event->GetParameters(), this ); subscene->SetSubScene( true ); event->SetSubScene( subscene ); // Now precache it's resources, if any BuildSortedSpeakEventSoundsPrefetchList( subscene, table, soundnames, event->GetStartTime() ); } } } break; } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneEntity::PausePlayback( void ) { if ( !m_bIsPlayingBack ) return; if ( m_bPaused ) return; m_bPaused = true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneEntity::ResumePlayback( void ) { if ( !m_bIsPlayingBack ) return; if ( !m_bPaused ) return; Assert( m_pScene ); if ( !m_pScene ) { // This should never happen!!!! return; } // FIXME: Iterate using m_pScene->IterateResumeConditionEvents and // only resume if the event conditions have all been satisfied // FIXME: Just resume for now m_pScene->ResumeSimulation(); m_bPaused = false; m_bPausedViaInput = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneEntity::CancelPlayback( void ) { if ( !m_bIsPlayingBack ) return; m_bIsPlayingBack = false; m_bPaused = false; m_OnCanceled.FireOutput( this, this, 0 ); LocalScene_Printf( "%s : %8.2f: canceled\n", STRING( m_iszSceneFile ), m_flCurrentTime ); OnSceneFinished( true, false ); } void CSceneEntity::PitchShiftPlayback( float fPitch ) { fPitch = clamp( fPitch, SCENE_MIN_PITCH, SCENE_MAX_PITCH ); m_fPitch = fPitch; if ( !m_pScene ) return; for ( int iActor = 0 ; iActor < m_pScene->GetNumActors(); ++iActor ) { CBaseFlex *pTestActor = FindNamedActor( iActor ); if ( !pTestActor ) continue; char szBuff[ 256 ]; if ( m_pScene->GetPlayingSoundName( szBuff, sizeof( szBuff ) ) ) { CPASAttenuationFilter filter( pTestActor ); EmitSound_t params; params.m_pSoundName = szBuff; params.m_nPitch = 100.0f * fPitch; params.m_nFlags = SND_CHANGE_PITCH; pTestActor->EmitSound( filter, pTestActor->entindex(), params ); } } } //----------------------------------------------------------------------------- // Purpose: Start a resume scene, if we have one, and resume playing when it finishes //----------------------------------------------------------------------------- void CSceneEntity::QueueResumePlayback( void ) { // Do we have a resume scene? if ( m_iszResumeSceneFile != NULL_STRING ) { bool bStartedScene = false; // If it has ".vcd" somewhere in the string, try using it as a scene file first if ( Q_stristr( STRING(m_iszResumeSceneFile), ".vcd" ) ) { bStartedScene = InstancedScriptedScene( NULL, STRING(m_iszResumeSceneFile), &m_hWaitingForThisResumeScene, 0, false ) != 0; } // HACKHACK: For now, get the first target, and see if we can find a response for him if ( !bStartedScene ) { CBaseFlex *pActor = FindNamedActor( 0 ); if ( pActor ) { CAI_BaseActor *pBaseActor = ToBaseActor( pActor ); if ( pBaseActor ) { AI_Response result ; AIConcept_t tmpConcept( STRING( m_iszResumeSceneFile ) ); if ( pBaseActor->FindResponse( result, tmpConcept, NULL ) ) { char response[ 256 ]; result.GetResponse( response, sizeof( response ) ); bStartedScene = InstancedScriptedScene( NULL, response, &m_hWaitingForThisResumeScene, 0, false ) != 0; } } } } // If we started a scene/response, wait for it to finish if ( bStartedScene ) { m_bWaitingForResumeScene = true; } else { // Failed to create the scene. Resume immediately. ResumePlayback(); } } else { // No resume scene, so just resume immediately ResumePlayback(); } } //----------------------------------------------------------------------------- // Purpose: Query whether the scene actually loaded. Only meaninful after Spawn() //----------------------------------------------------------------------------- bool CSceneEntity::ValidScene() const { return ( m_pScene != NULL ); } //----------------------------------------------------------------------------- // Purpose: // Input : *pActor - // *scene - // *event - //----------------------------------------------------------------------------- void CSceneEntity::DispatchStartSubScene( CChoreoScene *scene, CBaseFlex *pActor, CChoreoEvent *event) { if ( !scene->IsSubScene() ) { CChoreoScene *subscene = event->GetSubScene(); if ( !subscene ) { Assert( 0 ); /* subscene = LoadScene( event->GetParameters() ); subscene->SetSubScene( true ); event->SetSubScene( subscene ); */ } if ( subscene ) { subscene->ResetSimulation(); } } } //----------------------------------------------------------------------------- // Purpose: All events are leading edge triggered // Input : currenttime - // *event - //----------------------------------------------------------------------------- void CSceneEntity::StartEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) { Assert( event ); if ( !Q_stricmp( event->GetName(), "NULL" ) ) { LocalScene_Printf( "%s : %8.2f: ignored %s\n", STRING( m_iszSceneFile ), currenttime, event->GetDescription() ); return; } CBaseFlex *pActor = NULL; CChoreoActor *actor = event->GetActor(); if ( actor && (event->GetType() != CChoreoEvent::SCRIPT) && (event->GetType() != CChoreoEvent::CAMERA) ) { pActor = FindNamedActor( actor ); if (pActor == NULL) { Warning( "CSceneEntity %s unable to find actor named \"%s\"\n", STRING(GetEntityName()), actor->GetName() ); return; } } LocalScene_Printf( "%s : %8.2f: start %s\n", STRING( m_iszSceneFile ), currenttime, event->GetDescription() ); switch ( event->GetType() ) { case CChoreoEvent::SUBSCENE: { if ( pActor && !IsMultiplayer() ) { DispatchStartSubScene( scene, pActor, event ); } } break; case CChoreoEvent::EXPRESSION: { if ( pActor && !IsMultiplayer() ) { DispatchStartExpression( scene, pActor, event ); } } break; case CChoreoEvent::FLEXANIMATION: { if ( pActor && !IsMultiplayer() ) { DispatchStartFlexAnimation( scene, pActor, event ); } } break; case CChoreoEvent::LOOKAT: { if ( pActor && !IsMultiplayer() ) { CBaseEntity *pActor2 = FindNamedEntity( event->GetParameters( ), pActor ); if ( pActor2 ) { // Huh? DispatchStartLookAt( scene, pActor, pActor2, event ); } else { Warning( "CSceneEntity %s unable to find actor named \"%s\"\n", STRING(GetEntityName()), event->GetParameters() ); } } } break; case CChoreoEvent::SPEAK: { if ( pActor ) { // Speaking is edge triggered // FIXME: dB hack. soundlevel needs to be moved into inside of wav? soundlevel_t iSoundlevel = SNDLVL_TALKING; if (event->GetParameters2()) { iSoundlevel = (soundlevel_t)atoi( event->GetParameters2() ); } DispatchStartSpeak( scene, pActor, event, iSoundlevel ); } } break; case CChoreoEvent::MOVETO: { // FIXME: make sure moveto's aren't edge triggered if ( !event->HasEndTime() ) { event->SetEndTime( event->GetStartTime() + 1.0 ); } if ( pActor && !IsMultiplayer() ) { CBaseEntity *pActor2 = NULL; if ( event->GetParameters3( ) && strlen( event->GetParameters3( ) ) > 0 ) { pActor2 = FindNamedEntityClosest( event->GetParameters( ), pActor, false, true, event->GetParameters3( ) ); } else { pActor2 = FindNamedEntity( event->GetParameters( ), pActor, false, true ); } if ( pActor2 ) { DispatchStartMoveTo( scene, pActor, pActor2, event ); } else { Warning( "CSceneEntity %s unable to find actor named \"%s\"\n", STRING(GetEntityName()), event->GetParameters() ); } } } break; case CChoreoEvent::FACE: { if ( pActor && !IsMultiplayer() ) { CBaseEntity *pActor2 = FindNamedEntity( event->GetParameters( ), pActor ); if ( pActor2 ) { DispatchStartFace( scene, pActor, pActor2, event ); } else { Warning( "CSceneEntity %s unable to find actor named \"%s\"\n", STRING(GetEntityName()), event->GetParameters() ); } } } break; case CChoreoEvent::GESTURE: { if ( pActor ) { DispatchStartGesture( scene, pActor, event ); } } break; case CChoreoEvent::GENERIC: { // If the first token in the parameters is "debugtext", print the rest of the text if ( event->GetParameters() && !Q_strncmp( event->GetParameters(), "debugtext", 9 ) ) { const char *pszText = event->GetParameters() + 10; hudtextparms_s tTextParam; tTextParam.x = -1; tTextParam.y = 0.65; tTextParam.effect = 0; tTextParam.r1 = 255; tTextParam.g1 = 170; tTextParam.b1 = 0; tTextParam.a1 = 255; tTextParam.r2 = 255; tTextParam.g2 = 170; tTextParam.b2 = 0; tTextParam.a2 = 255; tTextParam.fadeinTime = 0; tTextParam.fadeoutTime = 0; tTextParam.holdTime = 3.1; tTextParam.fxTime = 0; tTextParam.channel = 1; UTIL_HudMessageAll( tTextParam, pszText ); break; } if ( pActor ) { DispatchStartGeneric( scene, pActor, event ); } } break; case CChoreoEvent::CAMERA: { // begin the camera shot const char *pszShotType = event->GetParameters(); CBaseEntity *pActor1 = FindNamedEntity( event->GetParameters2( ), pActor ); CBaseEntity *pActor2 = FindNamedEntity( event->GetParameters3( ), pActor ); float duration = event->GetDuration(); // grab any camera we find in the map // TODO: find camera that is nearest this scene entity? CTriggerCamera *pCamera = (CTriggerCamera *)gEntList.FindEntityByClassname( NULL, "point_viewcontrol" ); if ( !pCamera ) { Warning( "CSceneEntity %s unable to find a camera (point_viewcontrol) in this map!\n", STRING(GetEntityName()) ); } else { pCamera->StartCameraShot( pszShotType, this, pActor1, pActor2, duration ); } } break; case CChoreoEvent::SCRIPT: { // NOTE: this is only used by auto-generated vcds to embed script commands to map entities. // vscript call - param1 is entity name, param2 is function name, param3 is function parameter string // calls a vscript function defined on the scope of the named CBaseEntity object/actor. // script call is of the format FunctionName(pActor, pThisSceneEntity, pszScriptParameters, duration) const char *pszActorName = event->GetParameters(); const char *pszFunctionName = event->GetParameters2(); const char *pszScriptParameters = event->GetParameters3(); float duration = event->GetDuration(); // TODO: should be new method CBaseEntity::CallScriptFunctionParams() CBaseEntity *pEntity = (CBaseEntity *)gEntList.FindEntityByName( NULL, pszActorName ); //START_VMPROFILE if ( !pEntity ) { Warning( "CSceneEntity::SCRIPT event - unable to find entity named '%s' in this map!\n", pszActorName ); } else { if( !pEntity->ValidateScriptScope() ) { DevMsg("\n***\nCChoreoEvent::SCRIPT - FAILED to create private ScriptScope. ABORTING script call\n***\n"); break; } HSCRIPT hFunc = pEntity->m_ScriptScope.LookupFunction( pszFunctionName ); if( hFunc ) { pEntity->m_ScriptScope.Call( hFunc, NULL, ToHScript(this), pszScriptParameters, duration ); pEntity->m_ScriptScope.ReleaseFunction( hFunc ); //UPDATE_VMPROFILE } else { Warning("CSceneEntity::SCRIPT event - '%s' entity has no script function '%s' defined!\n", pszActorName,pszFunctionName); } } } break; case CChoreoEvent::FIRETRIGGER: { // Don't re-fire triggers during restore, the entities should already reflect all such state... if ( m_bRestoring ) { break; } CBaseEntity *pActivator = pActor; if (!pActivator) { pActivator = this; } // FIXME: how do I decide who fired it?? switch( atoi( event->GetParameters() ) ) { case 1: m_OnTrigger1.FireOutput( pActivator, this, 0 ); break; case 2: m_OnTrigger2.FireOutput( pActivator, this, 0 ); break; case 3: m_OnTrigger3.FireOutput( pActivator, this, 0 ); break; case 4: m_OnTrigger4.FireOutput( pActivator, this, 0 ); break; case 5: m_OnTrigger5.FireOutput( pActivator, this, 0 ); break; case 6: m_OnTrigger6.FireOutput( pActivator, this, 0 ); break; case 7: m_OnTrigger7.FireOutput( pActivator, this, 0 ); break; case 8: m_OnTrigger8.FireOutput( pActivator, this, 0 ); break; case 9: m_OnTrigger9.FireOutput( pActivator, this, 0 ); break; case 10: m_OnTrigger10.FireOutput( pActivator, this, 0 ); break; case 11: m_OnTrigger11.FireOutput( pActivator, this, 0 ); break; case 12: m_OnTrigger12.FireOutput( pActivator, this, 0 ); break; case 13: m_OnTrigger13.FireOutput( pActivator, this, 0 ); break; case 14: m_OnTrigger14.FireOutput( pActivator, this, 0 ); break; case 15: m_OnTrigger15.FireOutput( pActivator, this, 0 ); break; case 16: m_OnTrigger16.FireOutput( pActivator, this, 0 ); break; } } break; case CChoreoEvent::SEQUENCE: { if ( pActor ) { DispatchStartSequence( scene, pActor, event ); } } break; case CChoreoEvent::SECTION: { if ( IsMultiplayer() ) break; // Pauses scene playback DispatchPauseScene( scene, event->GetParameters() ); } break; case CChoreoEvent::LOOP: { DispatchProcessLoop( scene, event ); } break; case CChoreoEvent::INTERRUPT: { if ( IsMultiplayer() ) break; DispatchStartInterrupt( scene, event ); } break; case CChoreoEvent::STOPPOINT: { if ( IsMultiplayer() ) break; DispatchStopPoint( scene, event->GetParameters() ); } break; case CChoreoEvent::PERMIT_RESPONSES: { if ( IsMultiplayer() ) break; DispatchStartPermitResponses( scene, pActor, event ); } break; default: { // FIXME: Unhandeled event // Assert(0); } break; } } //----------------------------------------------------------------------------- // Purpose: // Input : currenttime - // *event - //----------------------------------------------------------------------------- void CSceneEntity::EndEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) { Assert( event ); if ( !Q_stricmp( event->GetName(), "NULL" ) ) { return; } CBaseFlex *pActor = NULL; CChoreoActor *actor = event->GetActor(); if ( actor ) { pActor = FindNamedActor( actor ); } LocalScene_Printf( "%s : %8.2f: finish %s\n", STRING( m_iszSceneFile ), currenttime, event->GetDescription() ); switch ( event->GetType() ) { case CChoreoEvent::EXPRESSION: { if ( pActor && !IsMultiplayer() ) { DispatchEndExpression( scene, pActor, event ); } } break; case CChoreoEvent::SPEAK: { if ( pActor ) { DispatchEndSpeak( scene, pActor, event ); } } break; case CChoreoEvent::FLEXANIMATION: { if ( pActor && !IsMultiplayer() ) { DispatchEndFlexAnimation( scene, pActor, event ); } } break; case CChoreoEvent::LOOKAT: { if ( pActor && !IsMultiplayer() ) { DispatchEndLookAt( scene, pActor, event ); } } break; case CChoreoEvent::GESTURE: { if ( pActor ) { DispatchEndGesture( scene, pActor, event ); } } break; case CChoreoEvent::GENERIC: { // If the first token in the parameters is "debugtext", we printed it and we're done if ( event->GetParameters() && !Q_strncmp( event->GetParameters(), "debugtext", 9 ) ) break; if ( pActor ) { DispatchEndGeneric( scene, pActor, event ); } } break; case CChoreoEvent::CAMERA: { // call the end of camera or call a dispatch function } break; case CChoreoEvent::SCRIPT: { // call the end of script or call a dispatch function } break; case CChoreoEvent::SEQUENCE: { if ( pActor ) { DispatchEndSequence( scene, pActor, event ); } } break; case CChoreoEvent::FACE: { if ( pActor && !IsMultiplayer() ) { DispatchEndFace( scene, pActor, event ); } } break; case CChoreoEvent::MOVETO: { if ( pActor && !IsMultiplayer() ) { DispatchEndMoveTo( scene, pActor, event ); } } break; case CChoreoEvent::SUBSCENE: { if ( IsMultiplayer() ) break; CChoreoScene *subscene = event->GetSubScene(); if ( subscene ) { subscene->ResetSimulation(); } } break; case CChoreoEvent::INTERRUPT: { if ( IsMultiplayer() ) break; DispatchEndInterrupt( scene, event ); } break; case CChoreoEvent::PERMIT_RESPONSES: { if ( IsMultiplayer() ) break; DispatchEndPermitResponses( scene, pActor, event ); } break; default: break; } } //----------------------------------------------------------------------------- // Purpose: Only spew one time per missing scene!!! // Input : *scenename - //----------------------------------------------------------------------------- void MissingSceneWarning( char const *scenename ) { static CUtlSymbolTable missing; // Make sure we only show the message once if ( UTL_INVAL_SYMBOL == missing.Find( scenename ) ) { missing.AddString( scenename ); Warning( "Scene '%s' missing!\n", scenename ); } } bool CSceneEntity::ShouldNetwork() const { if ( m_bMultiplayer ) { if ( m_pScene && ( m_pScene->HasEventsOfType( CChoreoEvent::FLEXANIMATION ) || m_pScene->HasEventsOfType( CChoreoEvent::EXPRESSION )|| m_pScene->HasEventsOfType( CChoreoEvent::GESTURE ) || m_pScene->HasEventsOfType( CChoreoEvent::SEQUENCE ) ) ) { return true; } } else { if ( m_pScene && ( m_pScene->HasEventsOfType( CChoreoEvent::FLEXANIMATION ) || m_pScene->HasEventsOfType( CChoreoEvent::EXPRESSION ) ) ) { return true; } } return false; } CChoreoScene *CSceneEntity::LoadScene( const char *filename, IChoreoEventCallback *pCallback ) { DevMsg( 2, "Blocking load of scene from '%s'\n", filename ); char loadfile[MAX_PATH]; Q_strncpy( loadfile, filename, sizeof( loadfile ) ); Q_SetExtension( loadfile, ".vcd", sizeof( loadfile ) ); Q_FixSlashes( loadfile ); // binary compiled vcd byte *pBuffer; int fileSize; if ( !CopySceneFileIntoMemory( loadfile, &pBuffer, &fileSize ) ) { MissingSceneWarning( loadfile ); return NULL; } CChoreoScene *pScene = new CChoreoScene( NULL ); CUtlBuffer buf( pBuffer, fileSize, CUtlBuffer::READ_ONLY ); if ( !pScene->RestoreFromBinaryBuffer( buf, loadfile, &g_ChoreoStringPool ) ) { Warning( "CSceneEntity::LoadScene: Unable to load binary scene '%s'\n", loadfile ); delete pScene; pScene = NULL; } else { pScene->SetPrintFunc( LocalScene_Printf ); pScene->SetEventCallbackInterface( pCallback ); } FreeSceneFileMemory( pBuffer ); return pScene; } CChoreoScene *BlockingLoadScene( const char *filename ) { return CSceneEntity::LoadScene( filename, NULL ); } //----------------------------------------------------------------------------- // Purpose: vscript - create a scene directly from a buffer containing // a vcd description, and load it into the scene entity. //----------------------------------------------------------------------------- bool CSceneEntity::ScriptLoadSceneFromString( const char * pszFilename, const char *pszData ) { CChoreoScene *pScene = new CChoreoScene( NULL ); // CSceneTokenProcessor SceneTokenProcessor; // SceneTokenProcessor.SetBuffer( pszData ); g_TokenProcessor.SetBuffer( (char *)pszData ); if ( !pScene->ParseFromBuffer( pszFilename, &g_TokenProcessor ) ) //&SceneTokenProcessor ) ) { Warning( "CSceneEntity::LoadSceneFromString: Unable to parse scene data '%s'\n", pszFilename ); delete pScene; pScene = NULL; } else { pScene->SetPrintFunc( LocalScene_Printf ); pScene->SetEventCallbackInterface( this ); // precache all sounds for the newly constructed scene PrecacheScene( pScene ); } if ( pScene != NULL ) { // release prior scene if present UnloadScene(); m_pScene = pScene; return true; } else { return false; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneEntity::UnloadScene( void ) { if ( m_pScene ) { ClearSceneEvents( m_pScene, false ); for ( int i = 0 ; i < m_pScene->GetNumActors(); i++ ) { CBaseFlex *pTestActor = FindNamedActor( i ); if ( !pTestActor ) continue; pTestActor->RemoveChoreoScene( m_pScene ); } } delete m_pScene; m_pScene = NULL; } //----------------------------------------------------------------------------- // Purpose: Called every frame that an event is active (Start/EndEvent as also // called) // Input : *event - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- void CSceneEntity::ProcessEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) { switch ( event->GetType() ) { case CChoreoEvent::SUBSCENE: { Assert( event->GetType() == CChoreoEvent::SUBSCENE ); CChoreoScene *subscene = event->GetSubScene(); if ( !subscene ) return; if ( subscene->SimulationFinished() ) return; // Have subscenes think for appropriate time subscene->Think( m_flFrameTime ); } break; default: break; } return; } //----------------------------------------------------------------------------- // Purpose: Called for events that are part of a pause condition // Input : *event - // Output : Returns true on event completed, false on non-completion. //----------------------------------------------------------------------------- bool CSceneEntity::CheckEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) { switch ( event->GetType() ) { case CChoreoEvent::SUBSCENE: { } break; default: { CBaseFlex *pActor = NULL; CChoreoActor *actor = event->GetActor(); if ( actor ) { pActor = FindNamedActor( actor ); if (pActor == NULL) { Warning( "CSceneEntity %s unable to find actor \"%s\"\n", STRING(GetEntityName()), actor->GetName() ); return true; } } if (pActor) { return pActor->CheckSceneEvent( currenttime, scene, event ); } } break; } return true; } //----------------------------------------------------------------------------- // Purpose: Get a sticky version of a named actor // Input : CChoreoActor // Output : CBaseFlex //----------------------------------------------------------------------------- CBaseFlex *CSceneEntity::FindNamedActor( int index ) { if (m_hActorList.Count() == 0) { m_hActorList.SetCount( m_pScene->GetNumActors() ); NetworkProp()->NetworkStateForceUpdate(); } if ( !m_hActorList.IsValidIndex( index ) ) { DevWarning( "Scene %s has %d actors, but scene entity only has %d actors\n", m_pScene->GetFilename(), m_pScene->GetNumActors(), m_hActorList.Count() ); return NULL; } CBaseFlex *pActor = m_hActorList[ index ]; if (pActor == NULL || !pActor->IsAlive() ) { CChoreoActor *pChoreoActor = m_pScene->GetActor( index ); if ( !pChoreoActor ) return NULL; pActor = FindNamedActor( pChoreoActor->GetName() ); if (pActor) { // save who we found so we'll use them again m_hActorList[ index ] = pActor; NetworkProp()->NetworkStateForceUpdate(); } } return pActor; } //----------------------------------------------------------------------------- // Purpose: Get a sticky version of a named actor // Input : CChoreoActor // Output : CBaseFlex //----------------------------------------------------------------------------- CBaseFlex *CSceneEntity::FindNamedActor( CChoreoActor *pChoreoActor ) { int index = m_pScene->FindActorIndex( pChoreoActor ); if (index >= 0) { return FindNamedActor( index ); } return NULL; } //----------------------------------------------------------------------------- // Purpose: Search for an actor by name, make sure it can do face poses // Input : *name - // Output : CBaseFlex //----------------------------------------------------------------------------- CBaseFlex *CSceneEntity::FindNamedActor( const char *name ) { CBaseEntity *entity = FindNamedEntity( name, NULL, true ); if ( !entity ) { // Couldn't find actor! return NULL; } // Make sure it can actually do facial animation, etc. CBaseFlex *flexEntity = dynamic_cast< CBaseFlex * >( entity ); if ( !flexEntity ) { // That actor was not a CBaseFlex! return NULL; } return flexEntity; } //----------------------------------------------------------------------------- // Purpose: Find an entity specified by a target name // Input : *name - // Output : CBaseEntity //----------------------------------------------------------------------------- CBaseEntity *CSceneEntity::FindNamedTarget( string_t iszTarget, bool bBaseFlexOnly ) { if ( !stricmp( STRING(iszTarget), "!activator" ) ) return m_hActivator; // If we don't have a wildcard in the target, just return the first entity found if ( !strchr( STRING(iszTarget), '*' ) ) return gEntList.FindEntityByName( NULL, iszTarget ); CBaseEntity *pTarget = NULL; while ( (pTarget = gEntList.FindEntityByName( pTarget, iszTarget )) != NULL ) { if ( bBaseFlexOnly ) { // Make sure it can actually do facial animation, etc. if ( dynamic_cast< CBaseFlex * >( pTarget ) ) return pTarget; } else { return pTarget; } } // Failed to find one return NULL; } //----------------------------------------------------------------------------- // Purpose: Filters entities only if they're clear //----------------------------------------------------------------------------- class CSceneFindMarkFilter : public IEntityFindFilter { public: void SetActor( CBaseEntity *pActor ) { m_hActor = pActor; } bool ShouldFindEntity( CBaseEntity *pEntity ) { if ( !m_hActor ) return true; // If we find no truly valid marks, we'll just use the first. if ( !m_hEntityFound.Get() ) { m_hEntityFound = pEntity; } // We only want marks that are clear trace_t tr; Vector vecOrigin = pEntity->GetAbsOrigin(); AI_TraceHull( vecOrigin, vecOrigin, m_hActor->WorldAlignMins(), m_hActor->WorldAlignMaxs(), MASK_SOLID, m_hActor, COLLISION_GROUP_NONE, &tr ); if ( tr.startsolid ) { return false; } m_hEntityFound = pEntity; return true; } CBaseEntity *GetFilterResult( void ) { return m_hEntityFound; } private: EHANDLE m_hActor; // To maintain backwards compatability, store off the first mark // we find. If we find no truly valid marks, we'll just use the first. EHANDLE m_hEntityFound; }; //----------------------------------------------------------------------------- // Purpose: Finds the entity nearest to both entities, and is clear //----------------------------------------------------------------------------- class CSceneFindNearestMarkFilter : public IEntityFindFilter { public: CSceneFindNearestMarkFilter( const CBaseEntity *pActor, const Vector &vecPos2, float flMaxRadius = MAX_TRACE_LENGTH ) { m_vecPos2 = vecPos2; m_flMaxSegmentDistance = flMaxRadius; m_flNearestToTarget = flMaxRadius; m_pNearestToTarget = NULL; m_flNearestToActor = flMaxRadius; m_pNearestToActor = NULL; m_hActor = pActor; if (pActor) { m_vecPos1 = pActor->GetAbsOrigin(); m_flMaxSegmentDistance = MIN( flMaxRadius, (m_vecPos1 - m_vecPos2).Length() + 1.0 ); if (m_flMaxSegmentDistance <= 1.0) { // must be closest to self m_flMaxSegmentDistance = MIN( flMaxRadius, MAX_TRACE_LENGTH ); } } } bool ShouldFindEntity( CBaseEntity *pEntity ) { if ( !m_hActor ) return true; // If we find no truly valid marks, we'll just use the first. if ( m_pNearestToActor == NULL ) { m_pNearestToActor = pEntity; } // We only want marks that are clear trace_t tr; Vector vecOrigin = pEntity->GetAbsOrigin(); AI_TraceHull( vecOrigin, vecOrigin, m_hActor->WorldAlignMins(), m_hActor->WorldAlignMaxs(), MASK_SOLID, m_hActor, COLLISION_GROUP_NONE, &tr ); if ( !tr.startsolid || tr.m_pEnt == m_hActor) { float dist1 = (m_vecPos1 - pEntity->GetAbsOrigin()).Length(); float dist2 = (m_vecPos2 - pEntity->GetAbsOrigin()).Length(); /* char text[256]; Q_snprintf( text, sizeof( text ), "%.0f : %.0f", dist1, dist2 ); NDebugOverlay::Text( pEntity->GetAbsOrigin() + Vector( 0, 0, 8 ), text, false, 5.0f ); */ // find the point closest to the actor if (dist1 <= m_flNearestToActor) { m_pNearestToActor = pEntity; m_flNearestToActor = dist2; } // find that node that's closest to both, but the distance to it from the actor isn't farther than // the distance to the second node. This should keep the actor from walking past their point of interest if (dist1 <= m_flMaxSegmentDistance && dist2 <= m_flMaxSegmentDistance && dist2 < m_flNearestToTarget) { m_pNearestToTarget = pEntity; m_flNearestToTarget = dist2; } } return false; } CBaseEntity *GetFilterResult( void ) { if (m_pNearestToTarget) return m_pNearestToTarget; return m_pNearestToActor; } private: EHANDLE m_hActor; Vector m_vecPos1; Vector m_vecPos2; float m_flMaxSegmentDistance; float m_flNearestToTarget; CBaseEntity *m_pNearestToTarget; float m_flNearestToActor; CBaseEntity *m_pNearestToActor; }; HSCRIPT CSceneEntity::ScriptFindNamedEntity( const char *name ) { return ToHScript(FindNamedEntity( name, NULL, false, false )); } //----------------------------------------------------------------------------- // Purpose: Search for an actor by name, make sure it can do face poses // Input : *name - // Output : CBaseFlex //----------------------------------------------------------------------------- CBaseEntity *CSceneEntity::FindNamedEntity( const char *name, CBaseEntity *pActor, bool bBaseFlexOnly, bool bUseClear ) { CBaseEntity *entity = NULL; if ( !stricmp( name, "Player" ) || !stricmp( name, "!player" )) { entity = ( gpGlobals->maxClients == 1 ) ? ( CBaseEntity * )UTIL_GetLocalPlayer() : NULL; } else if ( !stricmp( name, "!target1" ) ) { if (m_hTarget1 == NULL) { m_hTarget1 = FindNamedTarget( m_iszTarget1, bBaseFlexOnly ); } return m_hTarget1; } else if ( !stricmp( name, "!target2" ) ) { if (m_hTarget2 == NULL) { m_hTarget2 = FindNamedTarget( m_iszTarget2, bBaseFlexOnly ); } return m_hTarget2; } else if ( !stricmp( name, "!target3" ) ) { if (m_hTarget3 == NULL) { m_hTarget3 = FindNamedTarget( m_iszTarget3, bBaseFlexOnly ); } return m_hTarget3; } else if ( !stricmp( name, "!target4" ) ) { if (m_hTarget4 == NULL) { m_hTarget4 = FindNamedTarget( m_iszTarget4, bBaseFlexOnly ); } return m_hTarget4; } else if ( !stricmp( name, "!target5" ) ) { if (m_hTarget5 == NULL) { m_hTarget5 = FindNamedTarget( m_iszTarget5, bBaseFlexOnly ); } return m_hTarget5; } else if ( !stricmp( name, "!target6" ) ) { if (m_hTarget6 == NULL) { m_hTarget6 = FindNamedTarget( m_iszTarget6, bBaseFlexOnly ); } return m_hTarget6; } else if ( !stricmp( name, "!target7" ) ) { if (m_hTarget7 == NULL) { m_hTarget7 = FindNamedTarget( m_iszTarget7, bBaseFlexOnly ); } return m_hTarget7; } else if ( !stricmp( name, "!target8" ) ) { if (m_hTarget8 == NULL) { m_hTarget8 = FindNamedTarget( m_iszTarget8, bBaseFlexOnly ); } return m_hTarget8; } else if (pActor && pActor->MyNPCPointer()) { CSceneFindMarkFilter *pFilter = NULL; if ( bUseClear ) { pFilter = new CSceneFindMarkFilter(); pFilter->SetActor( pActor ); } entity = pActor->MyNPCPointer()->FindNamedEntity( name, pFilter ); if ( !entity && pFilter ) { entity = pFilter->GetFilterResult(); } } else { // search for up to 32 entities with the same name and choose one randomly CBaseEntity *entityList[ FINDNAMEDENTITY_MAX_ENTITIES ]; int iCount; entity = NULL; for( iCount = 0; iCount < FINDNAMEDENTITY_MAX_ENTITIES; iCount++ ) { entity = gEntList.FindEntityByName( entity, name, NULL, pActor ); if ( !entity ) { break; } entityList[ iCount ] = entity; } if ( iCount > 0 ) { entity = entityList[ RandomInt( 0, iCount - 1 ) ]; } else { entity = NULL; } } return entity; } //----------------------------------------------------------------------------- // Purpose: Search for an actor by name, make sure it can do face poses // Input : *name - // Output : CBaseFlex //----------------------------------------------------------------------------- CBaseEntity *CSceneEntity::FindNamedEntityClosest( const char *name, CBaseEntity *pActor, bool bBaseFlexOnly, bool bUseClear, const char *pszSecondary ) { CBaseEntity *entity = NULL; if ( !stricmp( name, "!activator" ) ) { return m_hActivator; } else if ( !stricmp( name, "Player" ) || !stricmp( name, "!player" )) { entity = ( gpGlobals->maxClients == 1 ) ? ( CBaseEntity * )UTIL_GetLocalPlayer() : NULL; return entity; } else if ( !stricmp( name, "!target1" ) ) { name = STRING( m_iszTarget1 ); } else if ( !stricmp( name, "!target2" ) ) { name = STRING( m_iszTarget2 ); } else if ( !stricmp( name, "!target3" ) ) { name = STRING( m_iszTarget3 ); } else if ( !stricmp( name, "!target4" ) ) { name = STRING( m_iszTarget4 ); } else if ( !stricmp( name, "!target5" ) ) { name = STRING( m_iszTarget5 ); } else if ( !stricmp( name, "!target6" ) ) { name = STRING( m_iszTarget6 ); } else if ( !stricmp( name, "!target7" ) ) { name = STRING( m_iszTarget7 ); } if (pActor && pActor->MyNPCPointer()) { if (pszSecondary && strlen( pszSecondary ) > 0) { CBaseEntity *pActor2 = FindNamedEntityClosest( pszSecondary, pActor, false, false, NULL ); if (pActor2) { CSceneFindNearestMarkFilter *pFilter = new CSceneFindNearestMarkFilter( pActor, pActor2->GetAbsOrigin() ); entity = pActor->MyNPCPointer()->FindNamedEntity( name, pFilter ); if (!entity && pFilter) { entity = pFilter->GetFilterResult(); } } } if (!entity) { CSceneFindMarkFilter *pFilter = NULL; if ( bUseClear ) { pFilter = new CSceneFindMarkFilter(); pFilter->SetActor( pActor ); } entity = pActor->MyNPCPointer()->FindNamedEntity( name, pFilter ); if (!entity && pFilter) { entity = pFilter->GetFilterResult(); } } } else { // search for up to 32 entities with the same name and choose one randomly int iCount; entity = NULL; CBaseEntity *current = NULL; for( iCount = 0; iCount < FINDNAMEDENTITY_MAX_ENTITIES; iCount++ ) { current = gEntList.FindEntityByName( current, name, NULL, pActor ); if ( current ) { if (RandomInt( 0, iCount ) == 0) entity = current; } } entity = NULL; } return entity; } //----------------------------------------------------------------------------- // Purpose: Remove all "scene" expressions from all actors in this scene //----------------------------------------------------------------------------- void CSceneEntity::ClearSceneEvents( CChoreoScene *scene, bool canceled ) { if ( !m_pScene ) return; LocalScene_Printf( "%s : %8.2f: clearing events\n", STRING( m_iszSceneFile ), m_flCurrentTime ); int i; for ( i = 0 ; i < m_pScene->GetNumActors(); i++ ) { CBaseFlex *pActor = FindNamedActor( i ); if ( !pActor ) continue; // Clear any existing expressions pActor->ClearSceneEvents( scene, canceled ); } // Iterate events and precache necessary resources for ( i = 0; i < scene->GetNumEvents(); i++ ) { CChoreoEvent *event = scene->GetEvent( i ); if ( !event ) continue; // load any necessary data switch (event->GetType() ) { default: break; case CChoreoEvent::SUBSCENE: { // Only allow a single level of subscenes for now if ( !scene->IsSubScene() ) { CChoreoScene *subscene = event->GetSubScene(); if ( subscene ) { ClearSceneEvents( subscene, canceled ); } } } break; } } } //----------------------------------------------------------------------------- // Purpose: Remove all imposed schedules from all actors in this scene //----------------------------------------------------------------------------- void CSceneEntity::ClearSchedules( CChoreoScene *scene ) { if ( !m_pScene ) return; int i; for ( i = 0 ; i < m_pScene->GetNumActors(); i++ ) { CBaseFlex *pActor = FindNamedActor( i ); if ( !pActor ) continue; CAI_BaseNPC *pNPC = pActor->MyNPCPointer(); if ( pNPC ) { /* if ( pNPC->IsCurSchedule( SCHED_SCENE_GENERIC ) ) pNPC->ClearSchedule( "Scene entity clearing all actor schedules" ); */ } else { pActor->ResetSequence( pActor->SelectWeightedSequence( ACT_IDLE ) ); pActor->SetCycle( 0 ); } // Clear any existing expressions } // Iterate events and precache necessary resources for ( i = 0; i < scene->GetNumEvents(); i++ ) { CChoreoEvent *event = scene->GetEvent( i ); if ( !event ) continue; // load any necessary data switch (event->GetType() ) { default: break; case CChoreoEvent::SUBSCENE: { // Only allow a single level of subscenes for now if ( !scene->IsSubScene() ) { CChoreoScene *subscene = event->GetSubScene(); if ( subscene ) { ClearSchedules( subscene ); } } } break; } } } //----------------------------------------------------------------------------- // Purpose: If we are currently interruptable, pause this scene and wait for the other // scene to finish // Input : *otherScene - //----------------------------------------------------------------------------- bool CSceneEntity::InterruptThisScene( CSceneEntity *otherScene ) { Assert( otherScene ); if ( !IsInterruptable() ) { return false; } // Already interrupted if ( m_bInterrupted ) { return false; } m_bInterrupted = true; m_hInterruptScene = otherScene; // Ask other scene to tell us when it's finished or canceled otherScene->RequestCompletionNotification( this ); PausePlayback(); return true; } /* void scene_interrupt( const CCommand &args ) { if ( args.ArgC() != 3 ) return; const char *scene1 = args[1]; const char *scene2 = args[2]; CSceneEntity *s1 = dynamic_cast< CSceneEntity * >( gEntList.FindEntityByName( NULL, scene1 ) ); CSceneEntity *s2 = dynamic_cast< CSceneEntity * >( gEntList.FindEntityByName( NULL, scene2 ) ); if ( !s1 || !s2 ) return; if ( s1->InterruptThisScene( s2 ) ) { s2->StartPlayback(); } } static ConCommand interruptscene( "int", scene_interrupt, "interrupt scene 1 with scene 2.", FCVAR_CHEAT ); */ //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneEntity::CheckInterruptCompletion() { if ( !m_bInterrupted ) return; // If the interruptor goes away it's the same as having that scene finish up... if ( m_hInterruptScene != NULL && !m_bInterruptSceneFinished ) { return; } m_bInterrupted = false; m_hInterruptScene = NULL; ResumePlayback(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneEntity::ClearInterrupt() { m_nInterruptCount = 0; m_bInterrupted = false; m_hInterruptScene = NULL; } //----------------------------------------------------------------------------- // Purpose: Another scene is asking us to notify upon completion // Input : *notify - //----------------------------------------------------------------------------- void CSceneEntity::RequestCompletionNotification( CSceneEntity *notify ) { CHandle< CSceneEntity > h; h = notify; // Only add it once if ( m_hNotifySceneCompletion.Find( h ) == m_hNotifySceneCompletion.InvalidIndex() ) { m_hNotifySceneCompletion.AddToTail( h ); } } //----------------------------------------------------------------------------- // Purpose: An interrupt scene has finished or been canceled, we can resume once we pick up this state in CheckInterruptCompletion // Input : *interruptor - //----------------------------------------------------------------------------- void CSceneEntity::NotifyOfCompletion( CSceneEntity *interruptor ) { Assert( m_bInterrupted ); Assert( m_hInterruptScene == interruptor ); m_bInterruptSceneFinished = true; CheckInterruptCompletion(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneEntity::AddListManager( CSceneListManager *pManager ) { CHandle< CSceneListManager > h; h = pManager; // Only add it once if ( m_hListManagers.Find( h ) == m_hListManagers.InvalidIndex() ) { m_hListManagers.AddToTail( h ); } } //----------------------------------------------------------------------------- // Purpose: Clear any targets that a referencing !activator //----------------------------------------------------------------------------- void CSceneEntity::ClearActivatorTargets( void ) { if ( !stricmp( STRING(m_iszTarget1), "!activator" ) ) { // We need to clear out actors so they're re-evaluated m_hActorList.Purge(); NetworkProp()->NetworkStateForceUpdate(); m_hTarget1 = NULL; } if ( !stricmp( STRING(m_iszTarget2), "!activator" ) ) { // We need to clear out actors so they're re-evaluated m_hActorList.Purge(); NetworkProp()->NetworkStateForceUpdate(); m_hTarget2 = NULL; } if ( !stricmp( STRING(m_iszTarget3), "!activator" ) ) { // We need to clear out actors so they're re-evaluated m_hActorList.Purge(); NetworkProp()->NetworkStateForceUpdate(); m_hTarget3 = NULL; } if ( !stricmp( STRING(m_iszTarget4), "!activator" ) ) { // We need to clear out actors so they're re-evaluated m_hActorList.Purge(); NetworkProp()->NetworkStateForceUpdate(); m_hTarget4 = NULL; } if ( !stricmp( STRING(m_iszTarget5), "!activator" ) ) { // We need to clear out actors so they're re-evaluated m_hActorList.Purge(); NetworkProp()->NetworkStateForceUpdate(); m_hTarget5 = NULL; } if ( !stricmp( STRING(m_iszTarget6), "!activator" ) ) { // We need to clear out actors so they're re-evaluated m_hActorList.Purge(); NetworkProp()->NetworkStateForceUpdate(); m_hTarget6 = NULL; } if ( !stricmp( STRING(m_iszTarget7), "!activator" ) ) { // We need to clear out actors so they're re-evaluated m_hActorList.Purge(); NetworkProp()->NetworkStateForceUpdate(); m_hTarget7 = NULL; } if ( !stricmp( STRING(m_iszTarget8), "!activator" ) ) { // We need to clear out actors so they're re-evaluated m_hActorList.Purge(); NetworkProp()->NetworkStateForceUpdate(); m_hTarget8 = NULL; } } //----------------------------------------------------------------------------- // Purpose: Called when a scene is completed or canceled //----------------------------------------------------------------------------- void CSceneEntity::OnSceneFinished( bool canceled, bool fireoutput ) { if ( !m_pScene ) return; LocalScene_Printf( "%s : %8.2f: finished\n", STRING( m_iszSceneFile ), m_flCurrentTime ); // Notify any listeners int c = m_hNotifySceneCompletion.Count(); int i; for ( i = 0; i < c; i++ ) { CSceneEntity *ent = m_hNotifySceneCompletion[ i ].Get(); if ( !ent ) continue; ent->NotifyOfCompletion( this ); } m_hNotifySceneCompletion.RemoveAll(); // Clear simulation m_pScene->ResetSimulation(); m_bIsPlayingBack = false; m_bPaused = false; SetCurrentTime( 0.0f, false ); // Clear interrupt state if we were interrupted for some reason ClearInterrupt(); if ( fireoutput && !m_bCompletedEarly) { m_OnCompletion.FireOutput( this, this, 0 ); } { CBaseFlex *pFlex = FindNamedActor( 0 ) ; if ( pFlex ) { CBaseMultiplayerPlayer *pAsPlayer = dynamic_cast(pFlex); if (pAsPlayer) { CAI_Expresser *pExpresser = pAsPlayer->GetExpresser(); pExpresser->OnSpeechFinished(); } else if ( CAI_BaseActor *pActor = ToBaseActor( pFlex ) ) { CAI_Expresser *pExpresser = pActor->GetExpresser(); pExpresser->OnSpeechFinished(); } } } // Put face back in neutral pose ClearSceneEvents( m_pScene, canceled ); for ( i = 0 ; i < m_pScene->GetNumActors(); i++ ) { CBaseFlex *pTestActor = FindNamedActor( i ); if ( !pTestActor ) continue; pTestActor->RemoveChoreoScene( m_pScene, canceled ); // If we interrupted the actor's previous scenes, resume them if ( m_bInterruptedActorsScenes ) { QueueActorsScriptedScenesToResume( pTestActor, false ); } } } //----------------------------------------------------------------------------- // Should we transmit it to the client? //----------------------------------------------------------------------------- int CSceneEntity::UpdateTransmitState() { if ( !ShouldNetwork() ) { return SetTransmitState( FL_EDICT_DONTSEND ); } if ( m_pRecipientFilter ) { return SetTransmitState( FL_EDICT_FULLCHECK ); } return SetTransmitState( FL_EDICT_ALWAYS ); } //----------------------------------------------------------------------------- // Purpose: Which clients should we be transmitting to? //----------------------------------------------------------------------------- int CSceneEntity::ShouldTransmit( const CCheckTransmitInfo *pInfo ) { int result = BaseClass::ShouldTransmit( pInfo ); // if we have excluded them via our recipient filter, don't send if ( m_pRecipientFilter && result != FL_EDICT_DONTSEND ) { bool bFound = false; // If we can't find them in the recipient list, exclude int i; for ( i=0; iGetRecipientCount();i++ ) { int iRecipient = m_pRecipientFilter->GetRecipientIndex(i); CBasePlayer *player = static_cast< CBasePlayer * >( CBaseEntity::Instance( iRecipient ) ); if ( player && ( player->edict() == pInfo->m_pClientEnt || player->IsSplitScreenUserOnEdict( pInfo->m_pClientEnt ) ) ) { bFound = true; break; } } if ( !bFound ) { result = FL_EDICT_DONTSEND; } } return result; } void CSceneEntity::SetRecipientFilter( IRecipientFilter *filter ) { // create a copy of this filter if ( filter ) { m_pRecipientFilter = new CRecipientFilter(); m_pRecipientFilter->CopyFrom( (CRecipientFilter &)( *filter ) ); } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- int CSceneEntity::DrawDebugTextOverlays() { int nOffset = BaseClass::DrawDebugTextOverlays(); if (m_debugOverlays & OVERLAY_TEXT_BIT) { char tempstr[512]; Q_snprintf( tempstr, sizeof( tempstr ), "Playing back: %s", m_bIsPlayingBack ? "yes" : "no" ); EntityText( nOffset, tempstr, 0 ); nOffset++; Q_snprintf( tempstr, sizeof( tempstr ), "Paused: %s", m_bPaused ? ( m_bPausedViaInput ? "yes - via input" : "yes" ) : "no" ); EntityText( nOffset, tempstr, 0 ); nOffset++; } return nOffset; } //----------------------------------------------------------------------------- // Purpose: Adds a player (by index) to the recipient filter //----------------------------------------------------------------------------- void CSceneEntity::AddBroadcastTeamTarget( int nTeamIndex ) { if ( m_pRecipientFilter == NULL ) { CRecipientFilter filter; SetRecipientFilter( &filter ); } CTeam *pTeam = GetGlobalTeam( nTeamIndex ); Assert( pTeam ); if ( pTeam == NULL ) return; m_pRecipientFilter->AddRecipientsByTeam( pTeam ); } //----------------------------------------------------------------------------- // Purpose: Removes a player (by index) from the recipient filter //----------------------------------------------------------------------------- void CSceneEntity::RemoveBroadcastTeamTarget( int nTeamIndex ) { if ( m_pRecipientFilter == NULL ) { CRecipientFilter filter; SetRecipientFilter( &filter ); } CTeam *pTeam = GetGlobalTeam( nTeamIndex ); Assert( pTeam ); if ( pTeam == NULL ) return; m_pRecipientFilter->RemoveRecipientsByTeam( pTeam ); } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- class CInstancedSceneEntity : public CSceneEntity { DECLARE_DATADESC(); DECLARE_CLASS( CInstancedSceneEntity, CSceneEntity ); public: EHANDLE m_hOwner; bool m_bHadOwner; float m_flPostSpeakDelay; float m_flPreDelay; char m_szInstanceFilename[ CChoreoScene::MAX_SCENE_FILENAME ]; bool m_bIsBackground; virtual void StartPlayback( void ); virtual void DoThink( float frametime ); virtual CBaseFlex *FindNamedActor( const char *name ); virtual CBaseEntity *FindNamedEntity( const char *name ); virtual float GetPostSpeakDelay() { return m_flPostSpeakDelay; } virtual void SetPostSpeakDelay( float flDelay ) { m_flPostSpeakDelay = flDelay; } virtual float GetPreDelay() { return m_flPreDelay; } virtual void SetPreDelay( float flDelay ) { m_flPreDelay = flDelay; } virtual void OnLoaded(); virtual void DispatchStartMoveTo( CChoreoScene *scene, CBaseFlex *actor, CBaseEntity *actor2, CChoreoEvent *event ) { if (PassThrough( actor )) BaseClass::DispatchStartMoveTo( scene, actor, actor2, event ); }; virtual void DispatchEndMoveTo( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { if (PassThrough( actor )) BaseClass::DispatchEndMoveTo( scene, actor, event ); }; virtual void DispatchStartFace( CChoreoScene *scene, CBaseFlex *actor, CBaseEntity *actor2, CChoreoEvent *event ) { if (PassThrough( actor )) BaseClass::DispatchStartFace( scene, actor, actor2, event ); }; virtual void DispatchEndFace( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { if (PassThrough( actor )) BaseClass::DispatchEndFace( scene, actor, event ); }; virtual void DispatchStartSequence( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { /* suppress */ }; virtual void DispatchEndSequence( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { /* suppress */ }; virtual void DispatchPauseScene( CChoreoScene *scene, const char *parameters ) { /* suppress */ }; void OnRestore(); virtual float EstimateLength( void ); private: bool PassThrough( CBaseFlex *actor ); }; LINK_ENTITY_TO_CLASS( instanced_scripted_scene, CInstancedSceneEntity ); //--------------------------------------------------------- // Save/Restore //--------------------------------------------------------- BEGIN_DATADESC( CInstancedSceneEntity ) DEFINE_FIELD( m_hOwner, FIELD_EHANDLE ), DEFINE_FIELD( m_bHadOwner, FIELD_BOOLEAN ), DEFINE_FIELD( m_flPostSpeakDelay, FIELD_FLOAT ), DEFINE_FIELD( m_flPreDelay, FIELD_FLOAT ), DEFINE_AUTO_ARRAY( m_szInstanceFilename, FIELD_CHARACTER ), DEFINE_FIELD( m_bIsBackground, FIELD_BOOLEAN ), END_DATADESC() int SceneNameAutocomplete( char const *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) { // chop the command off the begining of the string char *commandName = "scene_playvcd"; int numMatches = 0; partial += Q_strlen( commandName ) + 1; int partialLength = Q_strlen( partial ); // split the current path into the directory and the file. char dirName[ 512 ]; Q_snprintf( dirName, sizeof( dirName ), "%sjunk", partial ); Q_ExtractFilePath( dirName, dirName, sizeof( dirName ) ); partial += Q_strlen( dirName ); partialLength = Q_strlen( partial ); // now rebuild our path so we can pick up any directory that might have been entered. char path[ 512 ]; Q_snprintf( path, sizeof( path ), "scenes/%s%s*.*", dirName, partial ); FileFindHandle_t findHandle; char txtFilenameNoExtension[ MAX_PATH ]; const char *txtFilename = filesystem->FindFirstEx( path, "MOD", &findHandle ); while ( txtFilename ) { if ( txtFilename[0] != '.' ) // skip the parent and current directories "." and ".." { Q_FileBase( txtFilename, txtFilenameNoExtension, sizeof( txtFilenameNoExtension ) ); if ( !Q_strnicmp( txtFilenameNoExtension, partial, partialLength ) ) { if ( filesystem->FindIsDirectory( findHandle ) ) { // Add the directory name with a slash Q_snprintf( commands[ numMatches++ ], COMMAND_COMPLETION_ITEM_LENGTH, "%s %s%s/", commandName, dirName, txtFilenameNoExtension ); } else { // Add the file name to the autocomplete array Q_snprintf( commands[ numMatches++ ], COMMAND_COMPLETION_ITEM_LENGTH, "%s %s%s", commandName, dirName, txtFilenameNoExtension ); } if ( numMatches == COMMAND_COMPLETION_MAXITEMS ) { filesystem->FindClose( findHandle ); return numMatches; } } } txtFilename = filesystem->FindNext( findHandle ); } filesystem->FindClose( findHandle ); return numMatches; } CON_COMMAND_F_COMPLETION( scene_playvcd, "Play the given VCD as an instanced scripted scene.", FCVAR_CHEAT, SceneNameAutocomplete ) { char vcdPath[ 512 ]; Q_snprintf( vcdPath, sizeof( vcdPath ), "scenes/%s.vcd", args[1] ); InstancedScriptedScene( NULL, vcdPath ); } //----------------------------------------------------------------------------- // Purpose: create a one-shot scene, no movement, sequences, etc. // Input : // Output : //----------------------------------------------------------------------------- float InstancedScriptedScene( CBaseFlex *pActor, const char *pszScene, EHANDLE *phSceneEnt, float flPostDelay, bool bIsBackground, AI_Response *response, bool bMultiplayer, IRecipientFilter *filter /* = NULL */ ) { VPROF( "InstancedScriptedScene" ); CInstancedSceneEntity *pScene = (CInstancedSceneEntity *)CBaseEntity::CreateNoSpawn( "instanced_scripted_scene", vec3_origin, vec3_angle ); // This code expands any $gender tags into male or female tags based on the gender of the actor (based on his/her .mdl) if ( pActor ) { pActor->GenderExpandString( pszScene, pScene->m_szInstanceFilename, sizeof( pScene->m_szInstanceFilename ) ); } else { Q_strncpy( pScene->m_szInstanceFilename, pszScene, sizeof( pScene->m_szInstanceFilename ) ); } pScene->m_iszSceneFile = MAKE_STRING( pScene->m_szInstanceFilename ); // FIXME: I should set my output to fire something that kills me.... // FIXME: add a proper initialization function pScene->m_hOwner = pActor; pScene->m_bHadOwner = pActor != NULL; pScene->m_bMultiplayer = bMultiplayer; pScene->SetPostSpeakDelay( flPostDelay ); DispatchSpawn( pScene ); pScene->Activate(); pScene->m_bIsBackground = bIsBackground; pScene->SetBackground( bIsBackground ); pScene->SetRecipientFilter( filter ); if ( response ) { float flPreDelay = response->GetPreDelay(); if ( flPreDelay ) { pScene->SetPreDelay( flPreDelay ); } } pScene->StartPlayback(); if ( response ) { // If the response wants us to abort on NPC state switch, remember that pScene->SetBreakOnNonIdle( response->ShouldBreakOnNonIdle() ); } if ( phSceneEnt ) { *phSceneEnt = pScene; } return pScene->EstimateLength(); } //----------------------------------------------------------------------------- // Purpose: // Input : *pActor - // *soundnmame - // *phSceneEnt - // Output : float //----------------------------------------------------------------------------- float InstancedAutoGeneratedSoundScene( CBaseFlex *pActor, char const *soundname, EHANDLE *phSceneEnt /*= NULL*/ ) { if ( !pActor ) { Warning( "InstancedAutoGeneratedSoundScene: Expecting non-NULL pActor for sound %s\n", soundname ); return 0; } CInstancedSceneEntity *pScene = (CInstancedSceneEntity *)CBaseEntity::CreateNoSpawn( "instanced_scripted_scene", vec3_origin, vec3_angle ); Q_strncpy( pScene->m_szInstanceFilename, UTIL_VarArgs( "AutoGenerated(%s)", soundname ), sizeof( pScene->m_szInstanceFilename ) ); pScene->m_iszSceneFile = MAKE_STRING( pScene->m_szInstanceFilename ); pScene->m_hOwner = pActor; pScene->m_bHadOwner = pActor != NULL; pScene->GenerateSoundScene( pActor, soundname ); pScene->Spawn(); pScene->Activate(); pScene->StartPlayback(); if ( phSceneEnt ) { *phSceneEnt = pScene; } return pScene->EstimateLength(); } //----------------------------------------------------------------------------- void StopScriptedScene( CBaseFlex *pActor, EHANDLE hSceneEnt ) { CBaseEntity *pEntity = hSceneEnt; CSceneEntity *pScene = dynamic_cast(pEntity); if ( pScene ) { LocalScene_Printf( "%s : stop scripted scene\n", STRING( pScene->m_iszSceneFile ) ); pScene->CancelPlayback(); } } //----------------------------------------------------------------------------- // Purpose: // Input : *pszScene - // Output : float //----------------------------------------------------------------------------- float GetSceneDuration( char const *pszScene ) { unsigned int msecs = 0; SceneCachedData_t cachedData; if ( scenefilecache->GetSceneCachedData( pszScene, &cachedData ) ) { msecs = cachedData.msecs; } return (float)msecs * 0.001f; } //----------------------------------------------------------------------------- // Purpose: // Input : *pszScene - // Output : int //----------------------------------------------------------------------------- int GetSceneSpeechCount( char const *pszScene ) { SceneCachedData_t cachedData; if ( scenefilecache->GetSceneCachedData( pszScene, &cachedData ) ) { return cachedData.numSounds; } return 0; } HSCRIPT ScriptCreateSceneEntity( const char* pszScene ) { if ( IsEntityCreationAllowedInScripts() == false ) { Warning( "VScript error: A script attempted to create a scene entity mid-game. Entity creation from scripts is only allowed during map init.\n" ); return NULL; } g_pScriptVM->RegisterClass( GetScriptDescForClass( CSceneEntity ) ); CSceneEntity *pScene = (CSceneEntity *)CBaseEntity::CreateNoSpawn( "logic_choreographed_scene", vec3_origin, vec3_angle ); if ( pScene ) { pScene->m_iszSceneFile = AllocPooledString( pszScene ); DispatchSpawn( pScene ); } return ToHScript( pScene ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CInstancedSceneEntity::StartPlayback( void ) { // Wait until our pre delay is over if ( GetPreDelay() ) return; BaseClass::StartPlayback(); } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- void CInstancedSceneEntity::DoThink( float frametime ) { CheckInterruptCompletion(); if ( m_flPreDelay > 0 ) { m_flPreDelay = MAX( 0, m_flPreDelay - frametime ); StartPlayback(); if ( !m_bIsPlayingBack ) return; } if ( !m_pScene || !m_bIsPlayingBack || ( m_bHadOwner && m_hOwner == NULL ) ) { UTIL_Remove( this ); return; } // catch bad pitch shifting from old save games Assert( m_fPitch >= SCENE_MIN_PITCH && m_fPitch <= SCENE_MAX_PITCH ); m_fPitch = clamp( m_fPitch, SCENE_MIN_PITCH, SCENE_MAX_PITCH ); if ( m_bPaused ) { PauseThink(); return; } float dt = frametime; m_pScene->SetSoundFileStartupLatency( GetSoundSystemLatency() ); // Tell scene to go m_pScene->Think( m_flCurrentTime ); // Drive simulation time for scene SetCurrentTime( m_flCurrentTime + dt * m_fPitch, false ); // Did we get to the end if ( m_pScene->SimulationFinished() ) { OnSceneFinished( false, false ); UTIL_Remove( this ); } } //----------------------------------------------------------------------------- // Purpose: Search for an actor by name, make sure it can do face poses // Input : *name - // Output : CBaseFlex //----------------------------------------------------------------------------- CBaseFlex *CInstancedSceneEntity::FindNamedActor( const char *name ) { if ( m_pScene->GetNumActors() == 1 || stricmp( name, "!self" ) == 0 ) { if ( m_hOwner != NULL ) { CBaseCombatCharacter *pCharacter = m_hOwner->MyCombatCharacterPointer(); if ( pCharacter ) { return pCharacter; } } } return BaseClass::FindNamedActor( name ); } //----------------------------------------------------------------------------- // Purpose: Search for an actor by name, make sure it can do face poses // Input : *name - // Output : CBaseFlex //----------------------------------------------------------------------------- CBaseEntity *CInstancedSceneEntity::FindNamedEntity( const char *name ) { CBaseEntity *pOther = NULL; if (m_hOwner != NULL) { CAI_BaseNPC *npc = m_hOwner->MyNPCPointer(); if (npc) { pOther = npc->FindNamedEntity( name ); } else if ( m_hOwner->MyCombatCharacterPointer() ) { pOther = m_hOwner; } } if (!pOther) { pOther = BaseClass::FindNamedEntity( name ); } return pOther; } //----------------------------------------------------------------------------- // Purpose: Suppress certain events when it's instanced since they're can cause odd problems // Input : actor // Output : true - the event should happen, false - it shouldn't //----------------------------------------------------------------------------- bool CInstancedSceneEntity::PassThrough( CBaseFlex *actor ) { if (!actor) return false; CAI_BaseNPC *myNpc = actor->MyNPCPointer( ); if (!myNpc) return false; if (myNpc->IsCurSchedule( SCHED_SCENE_GENERIC )) { return true; } if (myNpc->GetCurSchedule()) { CAI_ScheduleBits testBits; myNpc->GetCurSchedule()->GetInterruptMask( &testBits ); if (testBits.IsBitSet( COND_IDLE_INTERRUPT )) { return true; } } LocalScene_Printf( "%s : event suppressed\n", STRING( m_iszSceneFile ) ); return false; } //----------------------------------------------------------------------------- void CInstancedSceneEntity::OnRestore() { if ( m_bHadOwner && !m_hOwner ) { // probably just came back from a level transition UTIL_Remove( this ); return; } // reset background state if ( m_pScene ) { m_pScene->SetBackground( m_bIsBackground ); } BaseClass::OnRestore(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CInstancedSceneEntity::EstimateLength( void ) { return (BaseClass::EstimateLength() + GetPreDelay()); } void CInstancedSceneEntity::OnLoaded() { BaseClass::OnLoaded(); SetBackground( m_bIsBackground ); } bool g_bClientFlex = true; LINK_ENTITY_TO_CLASS( scene_manager, CSceneManager ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneManager::Think() { // Latch this only once per frame... g_bClientFlex = scene_clientflex.GetBool(); // The manager is always thinking at 20 hz SetNextThink( gpGlobals->curtime + SCENE_THINK_INTERVAL ); float frameTime = ( gpGlobals->curtime - GetLastThink() ); frameTime = MIN( 0.1, frameTime ); // stop if AI is diabled if (CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI) return; bool needCleanupPass = false; int c = m_ActiveScenes.Count(); for ( int i = 0; i < c; i++ ) { CSceneEntity *scene = m_ActiveScenes[ i ].Get(); if ( !scene ) { needCleanupPass = true; continue; } scene->DoThink( frameTime ); if ( m_ActiveScenes.Count() < c ) { // Scene removed self while thinking. Adjust iteration. c = m_ActiveScenes.Count(); i--; } } // Now delete any invalid ones if ( needCleanupPass ) { for ( int i = c - 1; i >= 0; i-- ) { CSceneEntity *scene = m_ActiveScenes[ i ].Get(); if ( scene ) continue; m_ActiveScenes.Remove( i ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneManager::ClearAllScenes() { m_ActiveScenes.RemoveAll(); } //----------------------------------------------------------------------------- // Purpose: // Input : *scene - //----------------------------------------------------------------------------- void CSceneManager::AddSceneEntity( CSceneEntity *scene ) { CHandle< CSceneEntity > h; h = scene; // Already added/activated if ( m_ActiveScenes.Find( h ) != m_ActiveScenes.InvalidIndex() ) { return; } m_ActiveScenes.AddToTail( h ); } //----------------------------------------------------------------------------- // Purpose: // Input : *scene - //----------------------------------------------------------------------------- void CSceneManager::RemoveSceneEntity( CSceneEntity *scene ) { CHandle< CSceneEntity > h; h = scene; m_ActiveScenes.FindAndRemove( h ); } //----------------------------------------------------------------------------- // Purpose: // Input : *player - //----------------------------------------------------------------------------- void CSceneManager::OnClientActive( CBasePlayer *player ) { int c = m_QueuedSceneSounds.Count(); for ( int i = 0; i < c; i++ ) { CRestoreSceneSound *sound = &m_QueuedSceneSounds[ i ]; if ( sound->actor == NULL ) continue; // Blow off sounds too far in past to encode over networking layer if ( fabs( 1000.0f * sound->time_in_past ) > MAX_SOUND_DELAY_MSEC ) continue; CPASAttenuationFilter filter( sound->actor ); EmitSound_t es; es.m_nChannel = CHAN_VOICE; es.m_flVolume = 1; es.m_pSoundName = sound->soundname; es.m_SoundLevel = sound->soundlevel; es.m_flSoundTime = gpGlobals->curtime - sound->time_in_past; EmitSound( filter, sound->actor->entindex(), es ); } m_QueuedSceneSounds.RemoveAll(); } //----------------------------------------------------------------------------- // Purpose: Stops scenes involving the specified actor //----------------------------------------------------------------------------- void CSceneManager::RemoveActorFromScenes( CBaseFlex *pActor, bool bInstancedOnly, bool bNonIdleOnly, const char *pszThisSceneOnly ) { int c = m_ActiveScenes.Count(); for ( int i = 0; i < c; i++ ) { CSceneEntity *pScene = m_ActiveScenes[ i ].Get(); if ( !pScene ) { continue; } // If only stopping instanced scenes, then skip it if it can't cast to an instanced scene if ( bInstancedOnly && ( dynamic_cast< CInstancedSceneEntity * >( pScene ) == NULL ) ) { continue; } if ( bNonIdleOnly && !pScene->ShouldBreakOnNonIdle() ) continue; if ( pScene->InvolvesActor( pActor ) ) { if ( pszThisSceneOnly && pszThisSceneOnly[0] ) { if ( Q_strcmp( pszThisSceneOnly, STRING(pScene->m_iszSceneFile) ) ) continue; } LocalScene_Printf( "%s : removed for '%s'\n", STRING( pScene->m_iszSceneFile ), pActor ? pActor->GetDebugName() : "NULL" ); pScene->CancelPlayback(); } } } //----------------------------------------------------------------------------- // Purpose: Pause scenes involving the specified actor //----------------------------------------------------------------------------- void CSceneManager::PauseActorsScenes( CBaseFlex *pActor, bool bInstancedOnly ) { int c = m_ActiveScenes.Count(); for ( int i = 0; i < c; i++ ) { CSceneEntity *pScene = m_ActiveScenes[ i ].Get(); if ( !pScene ) { continue; } // If only stopping instanced scenes, then skip it if it can't cast to an instanced scene if ( bInstancedOnly && ( dynamic_cast< CInstancedSceneEntity * >( pScene ) == NULL ) ) { continue; } if ( pScene->InvolvesActor( pActor ) && pScene->IsPlayingBack() ) { LocalScene_Printf( "Pausing actor %s scripted scene: %s\n", pActor->GetDebugName(), STRING( pScene->m_iszSceneFile ) ); variant_t emptyVariant; pScene->AcceptInput( "Pause", pScene, pScene, emptyVariant, 0 ); } } } //----------------------------------------------------------------------------- // Purpose: Return true if this Actor is only in scenes that are interruptable right now //----------------------------------------------------------------------------- bool CSceneManager::IsInInterruptableScenes( CBaseFlex *pActor ) { int c = m_ActiveScenes.Count(); for ( int i = 0; i < c; i++ ) { CSceneEntity *pScene = m_ActiveScenes[ i ].Get(); if ( !pScene ) continue; //Ignore background scenes since they're harmless. if ( pScene->IsBackground() == true ) continue; if ( pScene->InvolvesActor( pActor ) && pScene->IsPlayingBack() ) { if ( pScene->IsInterruptable() == false ) return false; } } return true; } //----------------------------------------------------------------------------- // Purpose: Resume any paused scenes involving the specified actor //----------------------------------------------------------------------------- void CSceneManager::ResumeActorsScenes( CBaseFlex *pActor, bool bInstancedOnly ) { int c = m_ActiveScenes.Count(); for ( int i = 0; i < c; i++ ) { CSceneEntity *pScene = m_ActiveScenes[ i ].Get(); if ( !pScene ) { continue; } // If only stopping instanced scenes, then skip it if it can't cast to an instanced scene if ( bInstancedOnly && ( dynamic_cast< CInstancedSceneEntity * >( pScene ) == NULL ) ) { continue; } if ( pScene->InvolvesActor( pActor ) && pScene->IsPlayingBack() ) { LocalScene_Printf( "Resuming actor %s scripted scene: %s\n", pActor->GetDebugName(), STRING( pScene->m_iszSceneFile ) ); variant_t emptyVariant; pScene->AcceptInput( "Resume", pScene, pScene, emptyVariant, 0 ); } } } //----------------------------------------------------------------------------- // Purpose: Set all paused, in-playback scenes to resume when the actor is ready //----------------------------------------------------------------------------- void CSceneManager::QueueActorsScenesToResume( CBaseFlex *pActor, bool bInstancedOnly ) { int c = m_ActiveScenes.Count(); for ( int i = 0; i < c; i++ ) { CSceneEntity *pScene = m_ActiveScenes[ i ].Get(); if ( !pScene ) { continue; } // If only stopping instanced scenes, then skip it if it can't cast to an instanced scene if ( bInstancedOnly && ( dynamic_cast< CInstancedSceneEntity * >( pScene ) == NULL ) ) { continue; } if ( pScene->InvolvesActor( pActor ) && pScene->IsPlayingBack() && pScene->IsPaused() ) { pScene->QueueResumePlayback(); } } } //----------------------------------------------------------------------------- // Purpose: returns if there are scenes involving the specified actor //----------------------------------------------------------------------------- bool CSceneManager::IsRunningScriptedScene( CBaseFlex *pActor, bool bIgnoreInstancedScenes ) { int c = m_ActiveScenes.Count(); for ( int i = 0; i < c; i++ ) { CSceneEntity *pScene = m_ActiveScenes[ i ].Get(); if ( !pScene || !pScene->IsPlayingBack() || ( bIgnoreInstancedScenes && dynamic_cast(pScene) != NULL ) ) { continue; } if ( pScene->InvolvesActor( pActor ) ) { return true; } } return false; } bool CSceneManager::IsRunningScriptedSceneAndNotPaused( CBaseFlex *pActor, bool bIgnoreInstancedScenes ) { int c = m_ActiveScenes.Count(); for ( int i = 0; i < c; i++ ) { CSceneEntity *pScene = m_ActiveScenes[ i ].Get(); if ( !pScene || !pScene->IsPlayingBack() || pScene->IsPaused() || ( bIgnoreInstancedScenes && dynamic_cast(pScene) != NULL ) ) { continue; } if ( pScene->InvolvesActor( pActor ) ) { return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: // Input : *pActor - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CSceneManager::IsRunningScriptedSceneWithSpeech( CBaseFlex *pActor, bool bIgnoreInstancedScenes ) { int c = m_ActiveScenes.Count(); for ( int i = 0; i < c; i++ ) { CSceneEntity *pScene = m_ActiveScenes[ i ].Get(); if ( !pScene || !pScene->IsPlayingBack() || ( bIgnoreInstancedScenes && dynamic_cast(pScene) != NULL ) ) { continue; } if ( pScene->InvolvesActor( pActor ) ) { if ( pScene->HasUnplayedSpeech() ) return true; } } return false; } bool CSceneManager::IsRunningScriptedSceneWithSpeechAndNotPaused( CBaseFlex *pActor, bool bIgnoreInstancedScenes ) { int c = m_ActiveScenes.Count(); for ( int i = 0; i < c; i++ ) { CSceneEntity *pScene = m_ActiveScenes[ i ].Get(); if ( !pScene || !pScene->IsPlayingBack() || pScene->IsPaused() || ( bIgnoreInstancedScenes && dynamic_cast(pScene) != NULL ) ) { continue; } if ( pScene->InvolvesActor( pActor ) ) { if ( pScene->HasUnplayedSpeech() ) return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: // Input : *actor - // *soundname - // soundlevel - // soundtime - //----------------------------------------------------------------------------- void CSceneManager::QueueRestoredSound( CBaseFlex *actor, char const *soundname, soundlevel_t soundlevel, float time_in_past ) { CRestoreSceneSound e; e.actor = actor; Q_strncpy( e.soundname, soundname, sizeof( e.soundname ) ); e.soundlevel = soundlevel; e.time_in_past = time_in_past; m_QueuedSceneSounds.AddToTail( e ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void RemoveActorFromScriptedScenes( CBaseFlex *pActor, bool instancedscenesonly, bool nonidlescenesonly, const char *pszThisSceneOnly ) { GetSceneManager()->RemoveActorFromScenes( pActor, instancedscenesonly, nonidlescenesonly, pszThisSceneOnly ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void PauseActorsScriptedScenes( CBaseFlex *pActor, bool instancedscenesonly ) { GetSceneManager()->PauseActorsScenes( pActor, instancedscenesonly ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool IsInInterruptableScenes( CBaseFlex *pActor ) { return GetSceneManager()->IsInInterruptableScenes( pActor ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void ResumeActorsScriptedScenes( CBaseFlex *pActor, bool instancedscenesonly ) { GetSceneManager()->ResumeActorsScenes( pActor, instancedscenesonly ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void QueueActorsScriptedScenesToResume( CBaseFlex *pActor, bool instancedscenesonly ) { GetSceneManager()->QueueActorsScenesToResume( pActor, instancedscenesonly ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool IsRunningScriptedScene( CBaseFlex *pActor, bool bIgnoreInstancedScenes ) { return GetSceneManager()->IsRunningScriptedScene( pActor, bIgnoreInstancedScenes ); } bool IsRunningScriptedSceneAndNotPaused( CBaseFlex *pActor, bool bIgnoreInstancedScenes ) { return GetSceneManager()->IsRunningScriptedSceneAndNotPaused( pActor, bIgnoreInstancedScenes ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool IsRunningScriptedSceneWithSpeech( CBaseFlex *pActor, bool bIgnoreInstancedScenes ) { return GetSceneManager()->IsRunningScriptedSceneWithSpeech( pActor, bIgnoreInstancedScenes ); } bool IsRunningScriptedSceneWithSpeechAndNotPaused( CBaseFlex *pActor, bool bIgnoreInstancedScenes ) { return GetSceneManager()->IsRunningScriptedSceneWithSpeechAndNotPaused( pActor, bIgnoreInstancedScenes ); } //=========================================================================================================== // SCENE LIST MANAGER //=========================================================================================================== LINK_ENTITY_TO_CLASS( logic_scene_list_manager, CSceneListManager ); BEGIN_DATADESC( CSceneListManager ) DEFINE_UTLVECTOR( m_hListManagers, FIELD_EHANDLE ), // Keys DEFINE_KEYFIELD( m_iszScenes[0], FIELD_STRING, "scene0" ), DEFINE_KEYFIELD( m_iszScenes[1], FIELD_STRING, "scene1" ), DEFINE_KEYFIELD( m_iszScenes[2], FIELD_STRING, "scene2" ), DEFINE_KEYFIELD( m_iszScenes[3], FIELD_STRING, "scene3" ), DEFINE_KEYFIELD( m_iszScenes[4], FIELD_STRING, "scene4" ), DEFINE_KEYFIELD( m_iszScenes[5], FIELD_STRING, "scene5" ), DEFINE_KEYFIELD( m_iszScenes[6], FIELD_STRING, "scene6" ), DEFINE_KEYFIELD( m_iszScenes[7], FIELD_STRING, "scene7" ), DEFINE_KEYFIELD( m_iszScenes[8], FIELD_STRING, "scene8" ), DEFINE_KEYFIELD( m_iszScenes[9], FIELD_STRING, "scene9" ), DEFINE_KEYFIELD( m_iszScenes[10], FIELD_STRING, "scene10" ), DEFINE_KEYFIELD( m_iszScenes[11], FIELD_STRING, "scene11" ), DEFINE_KEYFIELD( m_iszScenes[12], FIELD_STRING, "scene12" ), DEFINE_KEYFIELD( m_iszScenes[13], FIELD_STRING, "scene13" ), DEFINE_KEYFIELD( m_iszScenes[14], FIELD_STRING, "scene14" ), DEFINE_KEYFIELD( m_iszScenes[15], FIELD_STRING, "scene15" ), DEFINE_FIELD( m_hScenes[0], FIELD_EHANDLE ), DEFINE_FIELD( m_hScenes[1], FIELD_EHANDLE ), DEFINE_FIELD( m_hScenes[2], FIELD_EHANDLE ), DEFINE_FIELD( m_hScenes[3], FIELD_EHANDLE ), DEFINE_FIELD( m_hScenes[4], FIELD_EHANDLE ), DEFINE_FIELD( m_hScenes[5], FIELD_EHANDLE ), DEFINE_FIELD( m_hScenes[6], FIELD_EHANDLE ), DEFINE_FIELD( m_hScenes[7], FIELD_EHANDLE ), DEFINE_FIELD( m_hScenes[8], FIELD_EHANDLE ), DEFINE_FIELD( m_hScenes[9], FIELD_EHANDLE ), DEFINE_FIELD( m_hScenes[10], FIELD_EHANDLE ), DEFINE_FIELD( m_hScenes[11], FIELD_EHANDLE ), DEFINE_FIELD( m_hScenes[12], FIELD_EHANDLE ), DEFINE_FIELD( m_hScenes[13], FIELD_EHANDLE ), DEFINE_FIELD( m_hScenes[14], FIELD_EHANDLE ), DEFINE_FIELD( m_hScenes[15], FIELD_EHANDLE ), // Inputs DEFINE_INPUTFUNC( FIELD_VOID, "Shutdown", InputShutdown ), END_DATADESC() //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneListManager::Activate( void ) { BaseClass::Activate(); // Hook up scenes, but not after loading a game because they're saved. if ( gpGlobals->eLoadType != MapLoad_LoadGame ) { for ( int i = 0; i < SCENE_LIST_MANAGER_MAX_SCENES; i++ ) { if ( m_iszScenes[i] != NULL_STRING ) { m_hScenes[i] = gEntList.FindEntityByName( NULL, STRING(m_iszScenes[i]) ); if ( m_hScenes[i] ) { CSceneEntity *pScene = dynamic_cast(m_hScenes[i].Get()); if ( pScene ) { pScene->AddListManager( this ); } else { CSceneListManager *pList = dynamic_cast(m_hScenes[i].Get()); if ( pList ) { pList->AddListManager( this ); } else { Warning( "%s(%s) found an entity that wasn't a logic_choreographed_scene or logic_scene_list_manager in slot %d, named %s\n", GetDebugName(), GetClassname(), i, STRING(m_iszScenes[i]) ); m_hScenes[i] = NULL; } } } else { Warning( "%s(%s) could not find scene %d, named %s\n", GetDebugName(), GetClassname(), i, STRING(m_iszScenes[i]) ); } } } } } //----------------------------------------------------------------------------- // Purpose: A scene or manager in our list has started playing. // Remove all scenes earlier in the list. //----------------------------------------------------------------------------- void CSceneListManager::SceneStarted( CBaseEntity *pSceneOrManager ) { // Move backwards and call remove on all scenes / managers earlier in the list to the fired one bool bFoundStart = false; for ( int i = SCENE_LIST_MANAGER_MAX_SCENES-1; i >= 0; i-- ) { if ( !m_hScenes[i] ) continue; if ( bFoundStart ) { RemoveScene( i ); } else if ( m_hScenes[i] == pSceneOrManager ) { bFoundStart = true; } } // Tell any managers we're within that we've started a scene if ( bFoundStart ) { int c = m_hListManagers.Count(); for ( int i = 0; i < c; i++ ) { if ( m_hListManagers[i] ) { m_hListManagers[i]->SceneStarted( this ); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneListManager::AddListManager( CSceneListManager *pManager ) { CHandle< CSceneListManager > h; h = pManager; // Only add it once if ( m_hListManagers.Find( h ) == m_hListManagers.InvalidIndex() ) { m_hListManagers.AddToTail( h ); } } //----------------------------------------------------------------------------- // Purpose: Shut down all scenes, and then remove this entity //----------------------------------------------------------------------------- void CSceneListManager::InputShutdown( inputdata_t &inputdata ) { ShutdownList(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneListManager::ShutdownList( void ) { for ( int i = 0; i < SCENE_LIST_MANAGER_MAX_SCENES; i++ ) { if ( m_hScenes[i] ) { RemoveScene(i); } } UTIL_Remove( this ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSceneListManager::RemoveScene( int iIndex ) { CSceneEntity *pScene = dynamic_cast(m_hScenes[iIndex].Get()); if ( pScene ) { // Remove the scene UTIL_Remove( pScene ); return; } // Tell the list manager to shut down all scenes CSceneListManager *pList = dynamic_cast(m_hScenes[iIndex].Get()); if ( pList ) { pList->ShutdownList(); } } void ReloadSceneFromDisk( CBaseEntity *ent ) { CSceneEntity *scene = dynamic_cast< CSceneEntity * >( ent ); if ( !scene ) return; Assert( 0 ); } // Purpose: // Input : *ent - // Output : char const //----------------------------------------------------------------------------- char const *GetSceneFilename( CBaseEntity *ent ) { CSceneEntity *scene = dynamic_cast< CSceneEntity * >( ent ); if ( !scene ) return ""; return STRING( scene->m_iszSceneFile ); } //----------------------------------------------------------------------------- // Purpose: Return a list of the last 5 lines of speech from NPCs for bug reports // Input : // Output : speech - last 5 sound files played as speech // returns the number of sounds in the returned list //----------------------------------------------------------------------------- int GetRecentNPCSpeech( recentNPCSpeech_t speech[ SPEECH_LIST_MAX_SOUNDS ] ) { int i; int num; int index; // clear out the output list for( i = 0; i < SPEECH_LIST_MAX_SOUNDS; i++ ) { speech[ i ].time = 0.0f; speech[ i ].name[ 0 ] = 0; speech[ i ].sceneName[ 0 ] = 0; } // copy the sound names into the list in order they were played num = 0; index = speechListIndex; for( i = 0; i < SPEECH_LIST_MAX_SOUNDS; i++ ) { if ( speechListSounds[ index ].name[ 0 ] ) { // only copy names that are not zero length speech[ num ] = speechListSounds[ index ]; num++; } index++; if ( index >= SPEECH_LIST_MAX_SOUNDS ) { index = 0; } } return num; } //----------------------------------------------------------------------------- // Purpose: Displays a list of the last 5 lines of speech from NPCs // Input : // Output : //----------------------------------------------------------------------------- static void ListRecentNPCSpeech( void ) { recentNPCSpeech_t speech[ SPEECH_LIST_MAX_SOUNDS ]; int num; int i; // get any sounds that were spoken by NPCs recently num = GetRecentNPCSpeech( speech ); Msg( "Recent NPC speech:\n" ); for( i = 0; i < num; i++ ) { Msg( " time: %6.3f sound name: %s scene: %s\n", speech[ i ].time, speech[ i ].name, speech[ i ].sceneName ); } Msg( "Current time: %6.3f\n", gpGlobals->curtime ); } static ConCommand ListRecentNPCSpeechCmd( "listRecentNPCSpeech", ListRecentNPCSpeech, "Displays a list of the last 5 lines of speech from NPCs.", FCVAR_DONTRECORD|FCVAR_GAMEDLL ); CON_COMMAND( scene_flush, "Flush all .vcds from the cache and reload from disk." ) { Msg( "Reloading\n" ); scenefilecache->Reload(); Msg( " done\n" ); }