This repository has been archived on 2024-06-13. You can view files and clone it, but cannot push or open issues or pull requests.
2020-08-04 13:13:01 -04:00

452 lines
16 KiB
C++

//========= 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<ConceptHistory_t, int> 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<CBaseFlex> 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 BASE_NPC>
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 <class BASE_NPC>
inline void CAI_ExpresserHost<BASE_NPC>::NoteSpeaking(float duration,
float delay) {
this->GetExpresser()->NoteSpeaking(duration, delay);
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline bool CAI_ExpresserHost<BASE_NPC>::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 <class BASE_NPC>
inline int CAI_ExpresserHost<BASE_NPC>::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 <class BASE_NPC>
inline void CAI_ExpresserHost<BASE_NPC>::ModifyOrAppendCriteria(
AI_CriteriaSet &criteriaSet) {
BaseClass::ModifyOrAppendCriteria(criteriaSet);
if (this->MyNPCPointer()) {
CAI_ExpresserHost_NPC_DoModifyOrAppendCriteria(this->MyNPCPointer(),
criteriaSet);
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline IResponseSystem *CAI_ExpresserHost<BASE_NPC>::GetResponseSystem() {
extern IResponseSystem *g_pResponseSystem;
// Expressive NPC's use the general response system
return g_pResponseSystem;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline bool CAI_ExpresserHost<BASE_NPC>::SpeakFindResponse(
AI_Response &response, AIConcept_t concept,
const char *modifiers /*= NULL*/) {
return this->GetExpresser()->SpeakFindResponse(response, concept,
modifiers);
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline bool CAI_ExpresserHost<BASE_NPC>::SpeakDispatchResponse(
AIConcept_t concept, AI_Response &response) {
if (this->GetExpresser()->SpeakDispatchResponse(concept, response)) {
PostSpeakDispatchResponse(concept, response);
return true;
}
return false;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline float CAI_ExpresserHost<BASE_NPC>::GetResponseDuration(
AI_Response &response) {
return this->GetExpresser()->GetResponseDuration(response);
}
//-----------------------------------------------------------------------------
// Override of base entity response input handler
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline void CAI_ExpresserHost<BASE_NPC>::DispatchResponse(
const char *conceptName) {
Speak((AIConcept_t)conceptName);
}
//-----------------------------------------------------------------------------
#endif // AI_SPEECH_H