sqwarmed/sdk_src/game/server/ai_speechqueue.h

240 lines
9.0 KiB
C++

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