|
|
//========= 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.
) RESTRICT;
/// 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
|