|
|
|
|
//========= Copyright <EFBFBD> 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
|