You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
239 lines
8.8 KiB
239 lines
8.8 KiB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: An event queue of AI concepts that dispatches them to appropriate characters. |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#ifndef AI_SPEECHQUEUE_H |
|
#define AI_SPEECHQUEUE_H |
|
|
|
#if defined( _WIN32 ) |
|
#pragma once |
|
#endif |
|
|
|
#include "ai_speech.h" |
|
|
|
#define AI_RESPONSE_QUEUE_SIZE 64 |
|
|
|
enum DeferredResponseTarget_t // possible targets for a deferred response |
|
{ |
|
kDRT_ANY, // best matching respondent within range -- except for the one in the m_hTarget handle |
|
kDRT_ALL, // send to everyone in range -- except for the one in the m_hTarget handle |
|
kDRT_SPECIFIC, // a specific entity is targeted |
|
|
|
kDRT_MAX, // high water mark |
|
}; |
|
|
|
// Allows you to postpone AI speech concepts to a later time, or to direct them to |
|
// a specific character, or all of them. |
|
class CResponseQueue |
|
{ |
|
//////////////////// Local types //////////////////// |
|
public: |
|
|
|
// We pack up contexts to send along with the concept. |
|
// For now I'll just copy criteria sets, but it will be better to do something |
|
// more efficient in the future. |
|
typedef AI_CriteriaSet DeferredContexts_t; |
|
|
|
struct CFollowupTargetSpec_t ///< to whom a followup is directed. Can be a specific entity or something more exotic. |
|
{ |
|
DeferredResponseTarget_t m_iTargetType; ///< ANY, ALL, or SPECIFIC. If specific, pass through a handle to: |
|
EHANDLE m_hHandle; ///< a specific target for the message, or a specific character to OMIT. |
|
inline bool IsValid( void ) const; |
|
|
|
// constructors/destructors |
|
explicit CFollowupTargetSpec_t(const DeferredResponseTarget_t &targetType, const EHANDLE &handle) |
|
: m_iTargetType(targetType), m_hHandle(handle) |
|
{}; |
|
explicit CFollowupTargetSpec_t(const EHANDLE &handle) |
|
: m_iTargetType(kDRT_SPECIFIC), m_hHandle(handle) |
|
{}; |
|
CFollowupTargetSpec_t(DeferredResponseTarget_t target) // eg, ANY, ALL, etc. |
|
: m_iTargetType(target) |
|
{ |
|
AssertMsg(m_iTargetType != kDRT_SPECIFIC, "Response rule followup tried to specify an entity target, but didn't provide the target.\n" ); |
|
} |
|
CFollowupTargetSpec_t(void) // default: invalid |
|
: m_iTargetType(kDRT_MAX) |
|
{}; |
|
}; |
|
|
|
/// A single deferred response. |
|
struct CDeferredResponse |
|
{ |
|
AIConcept_t m_concept; |
|
DeferredContexts_t m_contexts; ///< contexts to send along with the concept |
|
float m_fDispatchTime; |
|
EHANDLE m_hIssuer; ///< an entity, if issued by an entity |
|
/* |
|
DeferredResponseTarget_t m_iTargetType; |
|
EHANDLE m_hTarget; // May be invalid. |
|
*/ |
|
CFollowupTargetSpec_t m_Target; |
|
|
|
inline void Init( const AIConcept_t &concept, const AI_CriteriaSet * RESTRICT contexts, float dtime, const CFollowupTargetSpec_t &target, CBaseEntity *pIssuer ); |
|
inline bool IsQuashed() { return !m_Target.IsValid(); } |
|
void Quash(); ///< make this response invalid. |
|
}; |
|
/// write |
|
static void DeferContextsFromCriteriaSet( DeferredContexts_t &contextsOut, const AI_CriteriaSet *criteriaIn ); |
|
|
|
//////////////////// Methods //////////////////// |
|
public: |
|
CResponseQueue( int queueSize ); |
|
|
|
/// Add a deferred response. |
|
void Add( const AIConcept_t &concept, ///< concept to dispatch |
|
const AI_CriteriaSet * RESTRICT contexts, ///< the contexts that come with it (may be NULL) |
|
float time, ///< when to dispatch it. You can specify a time of zero to mean "immediately." |
|
const CFollowupTargetSpec_t &targetspec, /// All information necessary to target this response |
|
CBaseEntity *pIssuer = NULL ///< the entity who should not respond if this is a ANY or ALL rule. (eg, don't let people talk to themselves.) |
|
); |
|
|
|
/// Remove all deferred responses matching the concept and issuer. |
|
void Remove( const AIConcept_t &concept, ///< concept to dispatch |
|
CBaseEntity * const pIssuer = NULL ///< the entity issuing the response, if one exists. |
|
); |
|
|
|
/// Remove all deferred responses queued to be spoken by given character |
|
void RemoveSpeechQueuedFor( const CBaseEntity *pSpeaker ); |
|
|
|
/// Empty out all pending events |
|
void Evacuate(); |
|
|
|
/// Go through and dispatch any deferred responses. |
|
void PerFrameDispatch(); |
|
|
|
/// Add an expressor owner to this queue. |
|
void AddExpresserHost(CBaseEntity *host); |
|
|
|
/// Remove an expresser host from this queue. |
|
void RemoveExpresserHost(CBaseEntity *host); |
|
|
|
/// Iterate over potential expressers for this queue |
|
inline int GetNumExpresserTargets() const; |
|
inline CBaseEntity *GetExpresserHost(int which) const; |
|
|
|
protected: |
|
/// Actually send off one response to a consumer |
|
/// Return true if dispatch succeeded |
|
bool DispatchOneResponse( CDeferredResponse &response ); |
|
|
|
private: |
|
/// Helper function for one case in DispatchOneResponse |
|
/// (for better organization) |
|
bool DispatchOneResponse_ThenANY( CDeferredResponse &response, AI_CriteriaSet * RESTRICT pDeferredCriteria, CBaseEntity * const RESTRICT pIssuer, float followupMaxDistSq ); |
|
|
|
//////////////////// Data //////////////////// |
|
protected: |
|
typedef CUtlFixedLinkedList< CDeferredResponse > QueueType_t; |
|
QueueType_t m_Queue; // the queue of deferred responses, will eventually be sorted |
|
/// Note about the queue type: if you move to replace it with a sorted priority queue, |
|
/// make sure it is a type such that an iterator is not invalidated by inserts and deletes. |
|
/// CResponseQueue::PerFrameDispatch() iterates over the queue calling DispatchOneResponse |
|
/// on each in turn, and those responses may very easily add new events to the queue. |
|
/// A crash will result if the iterator used in CResponseQueue::PerFrameDispatch()'s loop |
|
/// becomes invalid. |
|
|
|
CUtlVector<EHANDLE> m_ExpresserTargets; // a list of legitimate expresser targets |
|
}; |
|
|
|
inline void CResponseQueue::CDeferredResponse::Init(const AIConcept_t &concept, const AI_CriteriaSet * RESTRICT contexts, float dtime, const CFollowupTargetSpec_t &target, CBaseEntity *pIssuer ) |
|
{ |
|
m_concept = concept; |
|
m_fDispatchTime = dtime; |
|
/* |
|
m_iTargetType = targetType; |
|
m_hTarget = handle ; |
|
*/ |
|
m_Target = target; |
|
m_hIssuer = pIssuer; |
|
DeferContextsFromCriteriaSet(m_contexts, contexts); |
|
} |
|
|
|
int CResponseQueue::GetNumExpresserTargets() const |
|
{ |
|
return m_ExpresserTargets.Count(); |
|
} |
|
|
|
CBaseEntity *CResponseQueue::GetExpresserHost(int which) const |
|
{ |
|
return m_ExpresserTargets[which]; |
|
} |
|
|
|
|
|
// The wrapper game system that contains a response queue, and ticks it each frame. |
|
|
|
class CResponseQueueManager : public CAutoGameSystemPerFrame |
|
{ |
|
public: |
|
CResponseQueueManager(char const *name) : CAutoGameSystemPerFrame( name ) |
|
{ |
|
m_pQueue = NULL; |
|
} |
|
virtual ~CResponseQueueManager(void); |
|
virtual void Shutdown(); |
|
virtual void FrameUpdatePostEntityThink( void ); |
|
virtual void LevelInitPreEntity( void ); |
|
|
|
inline CResponseQueue *GetQueue(void) { Assert(m_pQueue); return m_pQueue; } |
|
|
|
protected: |
|
CResponseQueue *m_pQueue; |
|
}; |
|
|
|
|
|
// Valid if the target type enum is within bounds. Furthermore if it |
|
// specifies a specific entity, that handle must be valid. |
|
bool CResponseQueue::CFollowupTargetSpec_t::IsValid( void ) const |
|
{ |
|
if (m_iTargetType >= kDRT_MAX) |
|
return false; |
|
if (m_iTargetType < 0) |
|
return false; |
|
if (m_iTargetType == kDRT_SPECIFIC && !m_hHandle.IsValid()) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
extern CResponseQueueManager g_ResponseQueueManager; |
|
|
|
|
|
// Handy global helper funcs |
|
|
|
/// Automatically queue up speech to happen immediately -- calls straight through to response rules add |
|
inline void QueueSpeak( const AIConcept_t &concept, ///< concept name to say |
|
const CResponseQueue::CFollowupTargetSpec_t& targetspec, ///< kDRT_ANY, kDRT_ALL, etc |
|
CBaseEntity *pIssuer = NULL ///< if specifying ANY or ALL, use this to specify the one you *don't* want to speak |
|
) |
|
{ |
|
return g_ResponseQueueManager.GetQueue()->Add( concept, NULL, 0.0f, targetspec, pIssuer ); |
|
} |
|
|
|
/// Automatically queue up speech to happen immediately -- calls straight through to response rules add |
|
inline void QueueSpeak( const AIConcept_t &concept, ///< concept name to say |
|
const CResponseQueue::CFollowupTargetSpec_t& targetspec, ///< kDRT_ANY, kDRT_ALL, etc |
|
const AI_CriteriaSet &criteria, ///< criteria to pass in |
|
CBaseEntity *pIssuer = NULL ///< if specifying ANY or ALL, use this to specify the one you *don't* want to speak |
|
) |
|
{ |
|
return g_ResponseQueueManager.GetQueue()->Add( concept, &criteria, 0.0f, targetspec, pIssuer ); |
|
} |
|
|
|
/// Automatically queue up speech to happen immediately -- calls straight through to response rules add |
|
inline void QueueSpeak( const AIConcept_t &concept, ///< concept name to say |
|
const EHANDLE &target, ///< which entity shall speak |
|
float delay, ///< how far in the future to speak |
|
const AI_CriteriaSet &criteria, ///< criteria to pass in |
|
CBaseEntity *pIssuer = NULL ) |
|
{ |
|
return g_ResponseQueueManager.GetQueue()->Add( concept, &criteria, gpGlobals->curtime + delay, |
|
CResponseQueue::CFollowupTargetSpec_t(target), pIssuer ); |
|
} |
|
|
|
|
|
|
|
#endif // AI_SPEECHQUEUE_H
|
|
|