//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #ifndef AI_SPEECH_H #define AI_SPEECH_H #include "utlmap.h" #include "AI_ResponseSystem.h" #include "soundflags.h" #include "utldict.h" #if defined(_WIN32) #pragma once #endif class KeyValues; class AI_CriteriaSet; //----------------------------------------------------------------------------- // Purpose: Used to share a global resource or prevent a system stepping on // own toes. //----------------------------------------------------------------------------- class CAI_TimedSemaphore { public: CAI_TimedSemaphore() : m_ReleaseTime(0) { m_hCurrentTalker = NULL; } void Acquire(float time, CBaseEntity *pTalker) { m_ReleaseTime = gpGlobals->curtime + time; m_hCurrentTalker = pTalker; } void Release() { m_ReleaseTime = 0; m_hCurrentTalker = NULL; } // Current owner of the semaphore is always allowed to talk bool IsAvailable(CBaseEntity *pTalker) const { return ((gpGlobals->curtime > m_ReleaseTime) || (m_hCurrentTalker == pTalker)); } float GetReleaseTime() const { return m_ReleaseTime; } CBaseEntity *GetOwner() { return m_hCurrentTalker; } private: float m_ReleaseTime; EHANDLE m_hCurrentTalker; }; //----------------------------------------------------------------------------- extern CAI_TimedSemaphore g_AIFriendliesTalkSemaphore; extern CAI_TimedSemaphore g_AIFoesTalkSemaphore; #define GetSpeechSemaphore(pNpc) \ (((pNpc)->IsPlayerAlly()) ? &g_AIFriendliesTalkSemaphore \ : &g_AIFoesTalkSemaphore) //----------------------------------------------------------------------------- // Basic speech system types //----------------------------------------------------------------------------- //------------------------------------- // Constants const float AIS_DEF_MIN_DELAY = 2.8; // Minimum amount of time an NPCs will wait after someone has spoken // before considering speaking again const float AIS_DEF_MAX_DELAY = 3.2; // Maximum amount of time an NPCs will wait after someone has spoken // before considering speaking again const float AIS_NO_DELAY = 0; const soundlevel_t AIS_DEF_SNDLVL = SNDLVL_TALKING; #define AI_NULL_CONCEPT NULL #define AI_NULL_SENTENCE NULL // Sentence prefix constants #define AI_SP_SPECIFIC_SENTENCE '!' #define AI_SP_WAVFILE '^' #define AI_SP_SCENE_GROUP '=' #define AI_SP_SPECIFIC_SCENE '?' #define AI_SPECIFIC_SENTENCE(str_constant) "!" str_constant #define AI_WAVFILE(str_constant) "^" str_constant // @Note (toml 09-12-02): as scene groups are not currently implemented, the // string is a semi-colon delimited list #define AI_SCENE_GROUP(str_constant) "=" str_constant #define AI_SPECIFIC_SCENE(str_constant) "?" str_constant // Designer overriding modifiers #define AI_SPECIFIC_SCENE_MODIFIER "scene:" //------------------------------------- //------------------------------------- // An id that represents the core meaning of a spoken phrase, // eventually to be mapped to a sentence group or scene typedef const char *AIConcept_t; inline bool CompareConcepts(AIConcept_t c1, AIConcept_t c2) { return ((void *)c1 == (void *)c2 || (c1 && c2 && Q_stricmp(c1, c2) == 0)); } //------------------------------------- // Specifies and stores the base timing and attentuation values for concepts // class AI_Response; //----------------------------------------------------------------------------- // CAI_Expresser // // Purpose: Provides the functionality of going from abstract concept ("hello") // to specific sentence/scene/wave // //------------------------------------- // Sink supports behavior control and receives notifications of internal events class CAI_ExpresserSink { public: virtual void OnSpokeConcept(AIConcept_t concept, AI_Response *response){}; virtual void OnStartSpeaking() {} virtual bool UseSemaphore() { return true; } }; struct ConceptHistory_t { DECLARE_SIMPLE_DATADESC(); ConceptHistory_t(float timeSpoken = -1) : timeSpoken(timeSpoken), response(NULL) {} ConceptHistory_t(const ConceptHistory_t &src); ConceptHistory_t &operator=(const ConceptHistory_t &src); ~ConceptHistory_t(); float timeSpoken; AI_Response *response; }; //------------------------------------- class CAI_Expresser : public IResponseFilter { public: CAI_Expresser(CBaseFlex *pOuter = NULL); ~CAI_Expresser(); // -------------------------------- bool Connect(CAI_ExpresserSink *pSink) { m_pSink = pSink; return true; } bool Disconnect(CAI_ExpresserSink *pSink) { m_pSink = NULL; return true; } void TestAllResponses(); // -------------------------------- bool Speak(AIConcept_t concept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL); // These two methods allow looking up a response and dispatching it to be // two different steps bool SpeakFindResponse(AI_Response &response, AIConcept_t concept, const char *modifiers = NULL); bool SpeakDispatchResponse(AIConcept_t concept, AI_Response &response, IRecipientFilter *filter = NULL); float GetResponseDuration(AI_Response &response); virtual int SpeakRawSentence(const char *pszSentence, float delay, float volume = VOL_NORM, soundlevel_t soundlevel = SNDLVL_TALKING, CBaseEntity *pListener = NULL); bool SemaphoreIsAvailable(CBaseEntity *pTalker); float GetSemaphoreAvailableTime(CBaseEntity *pTalker); // -------------------------------- virtual bool IsSpeaking(); bool CanSpeak(); bool CanSpeakAfterMyself(); float GetTimeSpeechComplete() const { return m_flStopTalkTime; } void BlockSpeechUntil(float time); // -------------------------------- bool CanSpeakConcept(AIConcept_t concept); bool SpokeConcept(AIConcept_t concept); float GetTimeSpokeConcept(AIConcept_t concept); // returns -1 if never void SetSpokeConcept(AIConcept_t concept, AI_Response *response, bool bCallback = true); void ClearSpokeConcept(AIConcept_t concept); // -------------------------------- void SetVoicePitch(int voicePitch) { m_voicePitch = voicePitch; } int GetVoicePitch() const; void NoteSpeaking(float duration, float delay = 0); // Force the NPC to release the semaphore & clear next speech time void ForceNotSpeaking(void); protected: CAI_TimedSemaphore *GetMySpeechSemaphore(CBaseEntity *pNpc); bool SpeakRawScene(const char *pszScene, float delay, AI_Response *response, IRecipientFilter *filter = NULL); // This will create a fake .vcd/CChoreoScene to wrap the sound to be played bool SpeakAutoGeneratedScene(char const *soundname, float delay); void DumpHistories(); void SpeechMsg(CBaseEntity *pFlex, PRINTF_FORMAT_STRING const char *pszFormat, ...); // -------------------------------- CAI_ExpresserSink *GetSink() { return m_pSink; } private: // -------------------------------- virtual bool IsValidResponse(ResponseType_t type, const char *pszValue); // -------------------------------- CAI_ExpresserSink *m_pSink; // -------------------------------- // // Speech concept data structures // CUtlDict m_ConceptHistories; // -------------------------------- // // Speaking states // float m_flStopTalkTime; // when in the future that I'll be done saying this // sentence. float m_flStopTalkTimeWithoutDelay; // same as the above, but minus the // delay before other people can speak float m_flBlockedTalkTime; int m_voicePitch; // pitch of voice for this head float m_flLastTimeAcceptedSpeak; // because speech may not be blocked until // NoteSpeaking called by scene ent, this // handles in-think blocking DECLARE_SIMPLE_DATADESC(); // -------------------------------- // public: virtual void SetOuter(CBaseFlex *pOuter) { m_pOuter = pOuter; } CBaseFlex *GetOuter() { return m_pOuter; } const CBaseFlex *GetOuter() const { return m_pOuter; } private: CHandle m_pOuter; }; class CMultiplayer_Expresser : public CAI_Expresser { public: CMultiplayer_Expresser(CBaseFlex *pOuter = NULL); //~CMultiplayer_Expresser(); virtual bool IsSpeaking(); void AllowMultipleScenes(); void DisallowMultipleScenes(); private: bool m_bAllowMultipleScenes; }; //----------------------------------------------------------------------------- // // An NPC base class to assist a branch of the inheritance graph // in utilizing CAI_Expresser // template class CAI_ExpresserHost : public BASE_NPC, protected CAI_ExpresserSink { DECLARE_CLASS_NOFRIEND(CAI_ExpresserHost, BASE_NPC); public: virtual void NoteSpeaking(float duration, float delay); virtual bool Speak(AIConcept_t concept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL); // These two methods allow looking up a response and dispatching it to be // two different steps bool SpeakFindResponse(AI_Response &response, AIConcept_t concept, const char *modifiers = NULL); bool SpeakDispatchResponse(AIConcept_t concept, AI_Response &response); virtual void PostSpeakDispatchResponse(AIConcept_t concept, AI_Response &response) { return; } float GetResponseDuration(AI_Response &response); float GetTimeSpeechComplete() const { return this->GetExpresser()->GetTimeSpeechComplete(); } bool IsSpeaking() { return this->GetExpresser()->IsSpeaking(); } bool CanSpeak() { return this->GetExpresser()->CanSpeak(); } bool CanSpeakAfterMyself() { return this->GetExpresser()->CanSpeakAfterMyself(); } void SetSpokeConcept(AIConcept_t concept, AI_Response *response, bool bCallback = true) { this->GetExpresser()->SetSpokeConcept(concept, response, bCallback); } float GetTimeSpokeConcept(AIConcept_t concept) { return this->GetExpresser()->GetTimeSpokeConcept(concept); } bool SpokeConcept(AIConcept_t concept) { return this->GetExpresser()->SpokeConcept(concept); } protected: int PlaySentence(const char *pszSentence, float delay, float volume = VOL_NORM, soundlevel_t soundlevel = SNDLVL_TALKING, CBaseEntity *pListener = NULL); virtual void ModifyOrAppendCriteria(AI_CriteriaSet &set); virtual IResponseSystem *GetResponseSystem(); // Override of base entity response input handler virtual void DispatchResponse(const char *conceptName); }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- template inline void CAI_ExpresserHost::NoteSpeaking(float duration, float delay) { this->GetExpresser()->NoteSpeaking(duration, delay); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- template inline bool CAI_ExpresserHost::Speak( AIConcept_t concept, const char *modifiers /*= NULL*/, char *pszOutResponseChosen /*=NULL*/, size_t bufsize /* = 0 */, IRecipientFilter *filter /* = NULL */) { AssertOnce(this->GetExpresser()->GetOuter() == this); return this->GetExpresser()->Speak(concept, modifiers, pszOutResponseChosen, bufsize, filter); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- template inline int CAI_ExpresserHost::PlaySentence(const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, CBaseEntity *pListener) { return this->GetExpresser()->SpeakRawSentence(pszSentence, delay, volume, soundlevel, pListener); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- extern void CAI_ExpresserHost_NPC_DoModifyOrAppendCriteria( CAI_BaseNPC *pSpeaker, AI_CriteriaSet &criteriaSet); template inline void CAI_ExpresserHost::ModifyOrAppendCriteria( AI_CriteriaSet &criteriaSet) { BaseClass::ModifyOrAppendCriteria(criteriaSet); if (this->MyNPCPointer()) { CAI_ExpresserHost_NPC_DoModifyOrAppendCriteria(this->MyNPCPointer(), criteriaSet); } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- template inline IResponseSystem *CAI_ExpresserHost::GetResponseSystem() { extern IResponseSystem *g_pResponseSystem; // Expressive NPC's use the general response system return g_pResponseSystem; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- template inline bool CAI_ExpresserHost::SpeakFindResponse( AI_Response &response, AIConcept_t concept, const char *modifiers /*= NULL*/) { return this->GetExpresser()->SpeakFindResponse(response, concept, modifiers); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- template inline bool CAI_ExpresserHost::SpeakDispatchResponse( AIConcept_t concept, AI_Response &response) { if (this->GetExpresser()->SpeakDispatchResponse(concept, response)) { PostSpeakDispatchResponse(concept, response); return true; } return false; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- template inline float CAI_ExpresserHost::GetResponseDuration( AI_Response &response) { return this->GetExpresser()->GetResponseDuration(response); } //----------------------------------------------------------------------------- // Override of base entity response input handler //----------------------------------------------------------------------------- template inline void CAI_ExpresserHost::DispatchResponse( const char *conceptName) { Speak((AIConcept_t)conceptName); } //----------------------------------------------------------------------------- #endif // AI_SPEECH_H