Added prototype Response System library port from the Alien Swarm SDK

This commit is contained in:
Blixibon 2021-03-08 02:11:13 -06:00
parent 251725c987
commit d081a0cee3
61 changed files with 11056 additions and 63 deletions

View File

@ -536,7 +536,6 @@ $Project
"$SRCDIR\public\dt_utlvector_recv.cpp" \
"$SRCDIR\public\filesystem_helpers.cpp" \
"$SRCDIR\public\interpolatortypes.cpp" \
"$SRCDIR\game\shared\interval.cpp" \
"$SRCDIR\common\language.cpp" \
"$SRCDIR\public\networkvar.cpp" \
"$SRCDIR\common\randoverride.cpp" \
@ -1107,6 +1106,7 @@ $Project
$File "$SRCDIR\public\vgui_controls\WizardSubPanel.h"
$File "$SRCDIR\public\worldsize.h"
$File "$SRCDIR\public\zip_uncompressed.h"
$File "$SRCDIR\public\tier1\interval.h"
//Haptics
$File "$SRCDIR\public\haptics\ihaptics.h" [$WIN32]
$File "$SRCDIR\public\haptics\haptic_utils.h" [$WIN32]
@ -1163,7 +1163,6 @@ $Project
$File "$SRCDIR\game\shared\igamesystem.h"
$File "$SRCDIR\game\shared\imovehelper.h"
$File "$SRCDIR\game\shared\in_buttons.h"
$File "$SRCDIR\game\shared\interval.h"
$File "$SRCDIR\game\shared\iplayeranimstate.h"
$File "$SRCDIR\game\shared\ipredictionsystem.h"
$File "$SRCDIR\game\shared\itempents.h"

View File

@ -12,6 +12,7 @@ $Configuration
$PreprocessorDefinitions "$BASE;MAPBASE_RPC;DISCORD_RPC;STEAM_RPC" [$MAPBASE_RPC]
$PreprocessorDefinitions "$BASE;MAPBASE_VSCRIPT" [$MAPBASE_VSCRIPT]
$PreprocessorDefinitions "$BASE;NEW_RESPONSE_SYSTEM" [$NEW_RESPONSE_SYSTEM]
}
}

View File

@ -4,6 +4,9 @@
//
//=============================================================================//
#ifdef NEW_RESPONSE_SYSTEM
#include "ai_criteria_new.h"
#else
#ifndef AI_CRITERIA_H
#define AI_CRITERIA_H
#ifdef _WIN32
@ -276,3 +279,4 @@ private:
};
#endif // AI_CRITERIA_H
#endif

View File

@ -2033,7 +2033,11 @@ bool CAI_BaseActor::UseSemaphore( void )
CAI_Expresser *CAI_BaseActor::CreateExpresser()
{
#ifdef NEW_RESPONSE_SYSTEM
m_pExpresser = new CAI_ExpresserWithFollowup(this);
#else
m_pExpresser = new CAI_Expresser(this);
#endif
return m_pExpresser;
}

View File

@ -15233,7 +15233,11 @@ void CAI_BaseNPC::CalculateValidEnemyInteractions( void )
const char *p = STRING(pInteraction->MiscCriteria);
while ( p )
{
#ifdef NEW_RESPONSE_SYSTEM
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL, STRING(pInteraction->MiscCriteria) );
#else
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL );
#endif
index = set.FindCriterionIndex(key);
if (index != -1)

View File

@ -64,7 +64,9 @@ class CBaseGrenade;
class CBaseDoor;
class CBasePropDoor;
struct AI_Waypoint_t;
#ifndef NEW_RESPONSE_SYSTEM
class AI_Response;
#endif
class CBaseFilter;
typedef CBitVec<MAX_CONDITIONS> CAI_ScheduleBits;

View File

@ -9,12 +9,19 @@
#include "simtimer.h"
#include "ai_behavior.h"
#ifdef NEW_RESPONSE_SYSTEM
#include "ai_speechconcept.h"
#endif
#if defined( _WIN32 )
#pragma once
#endif
#ifdef NEW_RESPONSE_SYSTEM
typedef CAI_Concept AIConcept_t;
#else
typedef const char *AIConcept_t;
#endif
// Speak concepts
#define TLK_LEAD_START "TLK_LEAD_START"

View File

@ -0,0 +1,471 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "ai_speech.h"
#include "game.h"
#include "eventqueue.h"
#include "ai_basenpc.h"
#include "basemultiplayerplayer.h"
#include "ai_baseactor.h"
#include "sceneentity.h"
//#include "flex_expresser.h"
/*
#include "engine/ienginesound.h"
#include "keyvalues.h"
#include "ai_criteria.h"
#include "isaverestore.h"
#include "sceneentity.h"
*/
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
static const char *GetResponseName( CBaseEntity *pEnt )
{
Assert( pEnt );
if ( pEnt == NULL )
return "";
return STRING( pEnt->GetEntityName() );
}
// This is a tiny helper function for below -- what I'd use a lambda for, usually
static void DispatchComeback( CAI_ExpresserWithFollowup *pExpress, CBaseEntity *pSpeaker, CBaseEntity *pRespondent, AI_ResponseFollowup &followup )
{
AssertMsg(pSpeaker != NULL, "Response expressor somehow got called with a NULL Outer.\n");
if ( !pRespondent )
{
return;
}
float delay = followup.followup_delay;
if (pSpeaker == pRespondent && delay < 0)
{
Warning("Response rule with a 'self' target specified negative delay, which isn't legal because that would make someone talk over himself.");
delay = 0;
}
// Msg( "%s: Dispatch comeback about %s to %s\n", pSpeaker->GetBotString(), g_pConceptManager->GetTopicName( handle ), pRespondent->GetBotString() );
// build an input event that we will use to force the bot to talk through the IO system
variant_t value;
// Don't send along null contexts
if (followup.followup_contexts && followup.followup_contexts[0] != '\0')
{
value.SetString( MAKE_STRING( followup.followup_contexts ) );
g_EventQueue.AddEvent( pRespondent, "AddContext", value, delay - 0.01, pSpeaker, pSpeaker );
}
/*
value.SetString(MAKE_STRING(followup.followup_concept));
g_EventQueue.AddEvent( pRespondent, "SpeakResponseConcept", value, delay , pSpeaker, pSpeaker );
*/
AI_CriteriaSet criteria;
// add in the FROM context so dispatchee knows was from me
const char * RESTRICT pszSpeakerName = GetResponseName( pSpeaker );
criteria.AppendCriteria( "From", pszSpeakerName );
// if a SUBJECT criteria is missing, put it back in.
if ( criteria.FindCriterionIndex( "Subject" ) == -1 )
{
criteria.AppendCriteria( "Subject", pszSpeakerName );
}
// add in any provided contexts from the parameters onto the ones stored in the followup
criteria.Merge( followup.followup_contexts );
// This is kludgy and needs to be fixed in class hierarchy, but for now, try to guess at the most likely
// kinds of targets and dispatch to them.
if (CBaseMultiplayerPlayer *pPlayer = dynamic_cast<CBaseMultiplayerPlayer *>(pRespondent))
{
pPlayer->Speak( followup.followup_concept, &criteria );
}
else if (CAI_BaseActor *pActor = dynamic_cast<CAI_BaseActor *>(pRespondent))
{
pActor->Speak( followup.followup_concept, &criteria );
}
}
#if 0
//-----------------------------------------------------------------------------
// Purpose: Placeholder for rules based response system
// Input : concept -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_ExpresserWithFollowup::Speak( AIConcept_t &concept, const char *modifiers /*= NULL*/, char *pszOutResponseChosen /* = NULL*/, size_t bufsize /* = 0 */, IRecipientFilter *filter /* = NULL */ )
{
AI_Response *result = SpeakFindResponse( concept, modifiers );
if ( !result )
{
return false;
}
CNPC_CompanionBot *pBot = dynamic_cast<CNPC_CompanionBot *>(GetOuter());
if ( pBot )
{
pBot->SetConversationTopic( g_pConceptManager->GetTopic( handle ) );
pBot->SetLastSpeaker( g_pConceptManager->GetSpeaker( handle ) );
// Msg( "%s: Conversing about %s\n", pBot->GetBotString(), g_pConceptManager->GetTopicName( handle ) );
}
SpeechMsg( GetOuter(), "%s (%x) spoke %s (%f)\n", STRING(GetOuter()->GetEntityName()), GetOuter(), g_pConceptManager->GetConcept( handle ), gpGlobals->curtime );
bool spoke = SpeakDispatchResponse( handle, result, filter );
if ( pszOutResponseChosen )
{
result->GetResponse( pszOutResponseChosen, bufsize );
}
return spoke;
}
#endif
// Work out the character from the "subject" context.
// Right now, this is a simple find by entity name search.
// But you can define arbitrary subject names, like L4D does
// for "biker", "manager", etc.
static CBaseEntity *AscertainSpeechSubjectFromContext( AI_Response *response, AI_CriteriaSet &criteria, const char *pContextName )
{
const char *subject = criteria.GetValue( criteria.FindCriterionIndex( pContextName ) );
if (subject)
{
return gEntList.FindEntityByName( NULL, subject );
}
else
{
return NULL;
}
}
// TODO: Currently uses awful stricmp. Use symbols! Once I know which ones we want, that is.
static CResponseQueue::CFollowupTargetSpec_t ResolveFollowupTargetToEntity( AIConcept_t &concept, AI_CriteriaSet &criteria, const char * RESTRICT szTarget, AI_Response * RESTRICT response = NULL )
{
if ( Q_stricmp(szTarget, "self") == 0 )
{
return CResponseQueue::CFollowupTargetSpec_t( kDRT_SPECIFIC, concept.GetSpeaker() );
}
else if ( Q_stricmp(szTarget, "subject") == 0 )
{
return CResponseQueue::CFollowupTargetSpec_t( AscertainSpeechSubjectFromContext( response, criteria, "Subject" ) );
}
else if ( Q_stricmp(szTarget, "from") == 0 )
{
return CResponseQueue::CFollowupTargetSpec_t( AscertainSpeechSubjectFromContext( response, criteria, "From" ) );
}
else if ( Q_stricmp(szTarget, "any") == 0 )
{
return CResponseQueue::CFollowupTargetSpec_t( kDRT_ANY, concept.GetSpeaker() );
}
else if ( Q_stricmp(szTarget, "all") == 0 )
{
return CResponseQueue::CFollowupTargetSpec_t( kDRT_ALL );
}
// last resort, try a named lookup
#ifdef MAPBASE
else if ( CBaseEntity *pSpecific = gEntList.FindEntityByName(NULL, szTarget, concept.GetSpeaker()) ) // it could be anything
#else
else if ( CBaseEntity *pSpecific = gEntList.FindEntityByName(NULL, szTarget) ) // it could be anything
#endif
{
return CResponseQueue::CFollowupTargetSpec_t( pSpecific );
}
Warning("Couldn't resolve response target %s\n", szTarget );
return CResponseQueue::CFollowupTargetSpec_t(); // couldn't resolve.
}
// TODO: Currently uses awful stricmp. Use symbols! Once I know which ones we want, that is.
static CResponseQueue::CFollowupTargetSpec_t ResolveFollowupTargetToEntity( AIConcept_t &concept, AI_CriteriaSet &criteria, AI_Response * RESTRICT response, AI_ResponseFollowup * RESTRICT followup )
{
const char * RESTRICT szTarget = followup->followup_target;
const CResponseQueue::CFollowupTargetSpec_t INVALID; // default: invalid result
if ( szTarget == NULL )
return INVALID;
else
return ResolveFollowupTargetToEntity( concept, criteria, szTarget, response );
}
ConVar chet_debug_idle( "chet_debug_idle", "0", FCVAR_ARCHIVE, "If set one, many debug prints to help track down the TLK_IDLE issue. Set two for super verbose info" );
// extern ConVar chet_debug_idle;
bool CAI_ExpresserWithFollowup::Speak( AIConcept_t &concept, const char *modifiers /*= NULL*/, char *pszOutResponseChosen /* = NULL*/, size_t bufsize /* = 0 */, IRecipientFilter *filter /* = NULL */ )
{
VPROF("CAI_Expresser::Speak");
if ( IsSpeechGloballySuppressed() )
{
return false;
}
concept.SetSpeaker(GetOuter());
AI_CriteriaSet criteria;
GatherCriteria(&criteria, concept, modifiers);
GetOuter()->ModifyOrAppendDerivedCriteria(criteria);
AI_Response result;
if ( !FindResponse( result, concept, &criteria ) )
{
if (chet_debug_idle.GetBool())
{
const char *name = GetOuter()->GetDebugName();
Msg( "TLK_IDLE: %s did not FindResponse\n", name );
}
return false;
}
else
{
if (chet_debug_idle.GetBool())
{
const char *name = GetOuter()->GetDebugName();
Msg( "TLK_IDLE: %s SUCCESSFUL FindResponse\n", name );
}
}
SpeechMsg( GetOuter(), "%s (%x) spoke %s (%f)", STRING(GetOuter()->GetEntityName()), GetOuter(), (const char*)concept, gpGlobals->curtime );
// Msg( "%s:%s to %s:%s\n", GetOuter()->GetDebugName(), concept.GetStringConcept(), criteria.GetValue(criteria.FindCriterionIndex("Subject")), pTarget ? pTarget->GetDebugName() : "none" );
bool spoke = SpeakDispatchResponse( concept, &result, &criteria, filter );
if ( pszOutResponseChosen )
{
result.GetResponse( pszOutResponseChosen, bufsize );
}
return spoke;
}
extern ISoundEmitterSystemBase* soundemitterbase;
static float GetSpeechDurationForResponse( const AI_Response * RESTRICT response, const char *szActorModel)
{
switch (response->GetType())
{
case ResponseRules::RESPONSE_SCENE:
{
char szScene[MAX_PATH];
soundemitterbase->GenderExpandString(szActorModel, response->GetResponsePtr(), szScene, MAX_PATH);
return GetSceneSpeechDuration(szScene);
}
break;
default:
break;
}
return 0.f;
}
//-----------------------------------------------------------------------------
// Purpose: Dispatches the result
// Input : *response -
//-----------------------------------------------------------------------------
bool CAI_ExpresserWithFollowup::SpeakDispatchResponse( AIConcept_t &concept, AI_Response *response, AI_CriteriaSet *criteria, IRecipientFilter *filter )
{
// This gives the chance for the other bot to respond.
if ( !concept.GetSpeaker().IsValid() )
{
concept.SetSpeaker(GetOuter());
}
bool bInterrupted = IsSpeaking();
bool bSuc = CAI_Expresser::SpeakDispatchResponse( concept, response, criteria, filter );
if (!bSuc)
{
return false;
}
if ( bInterrupted )
{
g_ResponseQueueManager.GetQueue()->RemoveSpeechQueuedFor( GetOuter() );
}
// Record my followup details so that I may defer its use til end of the speech
AI_ResponseFollowup * RESTRICT followup = response->GetParams()->m_pFollowup;
if ( followup )
{
if ( followup->followup_entityiotarget && followup->followup_entityioinput )
{
#ifdef MAPBASE
CBaseEntity * RESTRICT pTarget = ResolveFollowupTargetToEntity( concept, *criteria, followup->followup_entityiotarget, response ).m_hHandle;
#else
CBaseEntity * RESTRICT pTarget = gEntList.FindEntityByName( NULL, followup->followup_entityiotarget );
#endif
if ( pTarget )
{
g_EventQueue.AddEvent( pTarget, followup->followup_entityioinput, variant_t(), followup->followup_entityiodelay, GetOuter(), GetOuter() );
}
}
if ( followup->IsValid() )
{
// 11th hour change: rather than trigger followups from the end of a VCD,
// instead fire it from the end of the last speech event in the VCD, because
// there's a multisecond facial relax delay built into the scene.
// The speech length is stored in the cache, so we can post the followup now.
if ( response->GetType() == ResponseRules::RESPONSE_SCENE &&
followup->followup_delay >= 0 )
{
float fTimeToLastSpeech = GetSpeechDurationForResponse( response, STRING(GetOuter()->GetModelName()) );
// failsafe
if ( fTimeToLastSpeech > 0 )
{
DispatchFollowupThroughQueue( followup->followup_concept, followup->followup_contexts,
ResolveFollowupTargetToEntity( concept, *criteria, response, followup ),
fTimeToLastSpeech + followup->followup_delay, GetOuter() );
}
else // error
{
// old way, copied from "else" below
m_pPostponedFollowup = followup;
if ( criteria )
m_followupTarget = ResolveFollowupTargetToEntity( concept, *criteria, response, m_pPostponedFollowup );
else
{
AI_CriteriaSet tmpCriteria;
m_followupTarget = ResolveFollowupTargetToEntity( concept, tmpCriteria, response, m_pPostponedFollowup );
}
}
}
else if ( followup->followup_delay < 0 )
{
// a negative delay has a special meaning. Usually the comeback dispatches after
// the currently said line is finished; the delay is added to that, to provide a
// pause between when character A finishes speaking and B begins.
// A negative delay (-n) actually means "dispatch the comeback n seconds
// after I start talking".
// In this case we do not need to postpone the followup; we just throw it directly
// into the queue.
DispatchFollowupThroughQueue( followup->followup_concept, followup->followup_contexts,
ResolveFollowupTargetToEntity( concept, *criteria, response, followup ),
-followup->followup_delay, GetOuter() );
}
else if ( response->GetType() == ResponseRules::RESPONSE_PRINT )
{ // zero-duration responses dispatch immediately via the queue (must be the queue bec.
// the m_pPostponedFollowup will never trigger)
DispatchFollowupThroughQueue( followup->followup_concept, followup->followup_contexts,
ResolveFollowupTargetToEntity( concept, *criteria, response, followup ),
followup->followup_delay, GetOuter() );
}
else
{
// this is kind of a quick patch to immediately deal with the issue of null criteria
// (arose while branching to main) without replumbing a bunch of stuff -- to be fixed
// 5.13.08 egr
m_pPostponedFollowup = followup;
if ( criteria )
m_followupTarget = ResolveFollowupTargetToEntity( concept, *criteria, response, m_pPostponedFollowup );
else
{
AI_CriteriaSet tmpCriteria;
m_followupTarget = ResolveFollowupTargetToEntity( concept, tmpCriteria, response, m_pPostponedFollowup );
}
}
}
}
return bSuc;
}
// This is a gimmick used when a negative delay is specified in a followup, which is a shorthand
// for "this many seconds after the beginning of the line" rather than "this may seconds after the end
// of the line", eg to create a THEN rule when two characters talk over each other.
// It's static to avoid accidental use of the postponed followup/target members.
void CAI_ExpresserWithFollowup::DispatchFollowupThroughQueue( const AIConcept_t &concept,
const char * RESTRICT criteriaStr,
const CResponseQueue::CFollowupTargetSpec_t &target,
float delay,
CBaseEntity * RESTRICT pOuter
)
{
AI_CriteriaSet criteria;
// Don't add my own criteria! GatherCriteria( &criteria, followup.followup_concept, followup.followup_contexts );
criteria.AppendCriteria( "From", STRING( pOuter->GetEntityName() ) );
criteria.Merge( criteriaStr );
g_ResponseQueueManager.GetQueue()->Add( concept, &criteria, gpGlobals->curtime + delay, target, pOuter );
}
//-----------------------------------------------------------------------------
// Purpose: Handles the new concept objects
//-----------------------------------------------------------------------------
void CAI_ExpresserWithFollowup::SpeakDispatchFollowup( AI_ResponseFollowup &followup )
{
if ( !m_followupTarget.IsValid() )
return;
// If a specific entity target is given, use the old pathway for now
if ( m_followupTarget.m_iTargetType == kDRT_SPECIFIC && followup.followup_delay == 0 )
{
CBaseEntity *pTarget = m_followupTarget.m_hHandle.Get();
if (!pTarget)
{
return;
}
DispatchComeback( this, GetOuter(), pTarget, followup );
}
else
{
DispatchFollowupThroughQueue( followup.followup_concept, followup.followup_contexts, m_followupTarget, followup.followup_delay, GetOuter() );
}
// clear out the followup member just in case.
m_pPostponedFollowup = NULL;
m_followupTarget.m_iTargetType = kDRT_MAX;
}
void CAI_ExpresserWithFollowup::OnSpeechFinished()
{
if (m_pPostponedFollowup && m_pPostponedFollowup->IsValid())
{
return SpeakDispatchFollowup(*m_pPostponedFollowup);
}
}
void CC_RR_ForceConcept_f( const CCommand &args )
{
if ( args.ArgC() < 3 )
{
Msg("USAGE: rr_forceconcept <target> <concept> \"criteria1:value1,criteria2:value2,...\"\n");
return;
}
AI_CriteriaSet criteria;
if ( args.ArgC() >= 3 )
{
const char *criteriastring = args[3];
criteria.Merge( criteriastring );
}
AIConcept_t concept( args[2] );
QueueSpeak( concept, ResolveFollowupTargetToEntity( concept, criteria, args[1] ), criteria );
}
static ConCommand rr_forceconcept( "rr_forceconcept", CC_RR_ForceConcept_f,
"fire a response concept directly at a given character.\n"
"USAGE: rr_forceconcept <target> <concept> \"criteria1:value1,criteria2:value2,...\"\n"
"criteria values are optional.\n"
, FCVAR_CHEAT );

View File

@ -137,12 +137,23 @@ bool ConceptStringLessFunc( const string_t &lhs, const string_t &rhs )
return CaselessStringLessThan( STRING(lhs), STRING(rhs) );
}
#ifdef NEW_RESPONSE_SYSTEM
bool ConceptInfoStringLessFunc( const AIConcept_t& lhs, const AIConcept_t& rhs )
{
return CaselessStringLessThan( lhs.GetStringConcept(), rhs.GetStringConcept() );
}
#endif
//-----------------------------------------------------------------------------
class CConceptInfoMap : public CUtlMap<AIConcept_t, ConceptInfo_t *> {
public:
CConceptInfoMap() :
#ifdef NEW_RESPONSE_SYSTEM
CUtlMap<AIConcept_t, ConceptInfo_t *>( ConceptInfoStringLessFunc )
#else
CUtlMap<AIConcept_t, ConceptInfo_t *>( CaselessStringLessThan )
#endif
{
for ( int i = 0; i < ARRAYSIZE(g_ConceptInfos); i++ )
{
@ -557,7 +568,11 @@ void CAI_PlayerAlly::PrescheduleThink( void )
if ( SelectNonCombatSpeech( &selection ) )
{
SetSpeechTarget( selection.hSpeechTarget );
#ifdef NEW_RESPONSE_SYSTEM
SpeakDispatchResponse( selection.concept.c_str(), &selection.Response );
#else
SpeakDispatchResponse( selection.concept.c_str(), selection.pResponse );
#endif
m_flNextIdleSpeechTime = gpGlobals->curtime + RandomFloat( 20,30 );
}
else
@ -599,12 +614,22 @@ bool CAI_PlayerAlly::SelectSpeechResponse( AIConcept_t concept, const char *pszM
{
if ( IsAllowedToSpeak( concept ) )
{
#ifdef NEW_RESPONSE_SYSTEM
bool result = SpeakFindResponse( pSelection->Response, concept, pszModifiers );
if ( result )
{
pSelection->concept = concept;
pSelection->hSpeechTarget = pTarget;
return true;
}
#else
AI_Response *pResponse = SpeakFindResponse( concept, pszModifiers );
if ( pResponse )
{
pSelection->Set( concept, pResponse, pTarget );
return true;
}
#endif
}
return false;
}
@ -614,7 +639,9 @@ bool CAI_PlayerAlly::SelectSpeechResponse( AIConcept_t concept, const char *pszM
void CAI_PlayerAlly::SetPendingSpeech( AIConcept_t concept, AI_Response *pResponse )
{
m_PendingResponse = *pResponse;
#ifndef NEW_RESPONSE_SYSTEM
pResponse->Release();
#endif
m_PendingConcept = concept;
m_TimePendingSet = gpGlobals->curtime;
}
@ -696,7 +723,11 @@ bool CAI_PlayerAlly::SelectInterjection()
if ( SelectIdleSpeech( &selection ) )
{
SetSpeechTarget( selection.hSpeechTarget );
#ifdef NEW_RESPONSE_SYSTEM
SpeakDispatchResponse( selection.concept.c_str(), &selection.Response );
#else
SpeakDispatchResponse( selection.concept.c_str(), selection.pResponse );
#endif
return true;
}
}
@ -881,6 +912,18 @@ bool CAI_PlayerAlly::AskQuestionNow( CBaseEntity *pSpeechTarget, int iQARandomNu
m_iQARandomNumber = RandomInt(0, 100);
AISpeechSelection_t selection;
#ifdef NEW_RESPONSE_SYSTEM
if (SelectSpeechResponse( concept, NULL, m_hPotentialSpeechTarget.Get(), &selection ))
{
SetSpeechTarget( selection.hSpeechTarget );
ClearPendingSpeech();
// Speak immediately
return SpeakDispatchResponse( selection.concept.c_str(), &selection.Response );
}
return false;
#else
SelectSpeechResponse( concept, NULL, m_hPotentialSpeechTarget.Get(), &selection );
SetSpeechTarget( selection.hSpeechTarget );
@ -891,6 +934,7 @@ bool CAI_PlayerAlly::AskQuestionNow( CBaseEntity *pSpeechTarget, int iQARandomNu
// Speak immediately
return SpeakDispatchResponse( selection.concept.c_str(), selection.pResponse );
#endif
}
//-----------------------------------------------------------------------------
@ -980,7 +1024,11 @@ void CAI_PlayerAlly::AnswerQuestion( CAI_PlayerAlly *pQuestioner, int iQARandomN
Assert( selection.pResponse );
SetSpeechTarget( selection.hSpeechTarget );
#ifdef NEW_RESPONSE_SYSTEM
SpeakDispatchResponse( selection.concept.c_str(), &selection.Response );
#else
SpeakDispatchResponse( selection.concept.c_str(), selection.pResponse );
#endif
// Prevent idle speech for a while
DeferAllIdleSpeech( random->RandomFloat( TALKER_DEFER_IDLE_SPEAK_MIN, TALKER_DEFER_IDLE_SPEAK_MAX ), GetSpeechTarget()->MyNPCPointer() );
@ -1032,7 +1080,11 @@ int CAI_PlayerAlly::SelectNonCombatSpeechSchedule()
{
Assert( selection.pResponse );
SetSpeechTarget( selection.hSpeechTarget );
#ifdef NEW_RESPONSE_SYSTEM
SetPendingSpeech( selection.concept.c_str(), &selection.Response );
#else
SetPendingSpeech( selection.concept.c_str(), selection.pResponse );
#endif
}
}
@ -1107,9 +1159,13 @@ void CAI_PlayerAlly::StartTask( const Task_t *pTask )
case TASK_TALKER_SPEAK_PENDING:
if ( !m_PendingConcept.empty() )
{
#ifdef NEW_RESPONSE_SYSTEM
SpeakDispatchResponse( m_PendingConcept.c_str(), &m_PendingResponse );
#else
AI_Response *pResponse = new AI_Response;
*pResponse = m_PendingResponse;
SpeakDispatchResponse( m_PendingConcept.c_str(), pResponse );
#endif
m_PendingConcept.erase();
TaskComplete();
}
@ -1844,6 +1900,18 @@ bool CAI_PlayerAlly::RespondedTo( const char *ResponseConcept, bool bForce, bool
{
// We're being forced to respond to the event, probably because it's the
// player dying or something equally important.
#ifdef NEW_RESPONSE_SYSTEM
AI_Response response;
bool result = SpeakFindResponse( response, ResponseConcept, NULL );
if ( result )
{
// We've got something to say. Stop any scenes we're in, and speak the response.
if ( bCancelScene )
RemoveActorFromScriptedScenes( this, false );
return SpeakDispatchResponse( ResponseConcept, &response );
}
#else
AI_Response *result = SpeakFindResponse( ResponseConcept, NULL );
if ( result )
{
@ -1854,6 +1922,7 @@ bool CAI_PlayerAlly::RespondedTo( const char *ResponseConcept, bool bForce, bool
bool spoke = SpeakDispatchResponse( ResponseConcept, result );
return spoke;
}
#endif
return false;
}

View File

@ -252,6 +252,11 @@ enum AISpeechTargetSearchFlags_t
struct AISpeechSelection_t
{
#ifdef NEW_RESPONSE_SYSTEM
std::string concept;
AI_Response Response;
EHANDLE hSpeechTarget;
#else
AISpeechSelection_t()
: pResponse(NULL)
{
@ -267,6 +272,7 @@ struct AISpeechSelection_t
std::string concept;
AI_Response * pResponse;
EHANDLE hSpeechTarget;
#endif
};
//-------------------------------------

View File

@ -5,6 +5,9 @@
// $NoKeywords: $
//=============================================================================//
#ifdef NEW_RESPONSE_SYSTEM
#include "ai_speech_new.h"
#else
#ifndef AI_SPEECH_H
#define AI_SPEECH_H
@ -452,3 +455,4 @@ inline void CAI_ExpresserHost<BASE_NPC>::DispatchResponse( const char *conceptNa
//-----------------------------------------------------------------------------
#endif // AI_SPEECH_H
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,664 @@
//========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef AI_SPEECH_H
#define AI_SPEECH_H
#include "utlmap.h"
#include "soundflags.h"
#include "AI_Criteria.h"
#include "ai_responsesystem.h"
#include "utldict.h"
#include "ai_speechconcept.h"
#if defined( _WIN32 )
#pragma once
#endif
class KeyValues;
using ResponseRules::ResponseType_t;
using ResponseRules::AI_ResponseFollowup;
//-----------------------------------------------------------------------------
// 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_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
#if AI_CONCEPTS_ARE_STRINGS
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 ) );
}
#else
typedef CAI_Concept AIConcept_t;
inline bool CompareConcepts( AIConcept_t c1, AIConcept_t c2 )
{
return c1.m_iConcept == c2.m_iConcept;
}
#endif
//-----------------------------------------------------------------------------
// 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 ), m_response( )
{
}
ConceptHistory_t( const ConceptHistory_t& src );
ConceptHistory_t& operator = ( const ConceptHistory_t& src );
~ConceptHistory_t();
float timeSpoken;
AI_Response m_response;
};
//-------------------------------------
class CAI_Expresser : public ResponseRules::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 );
bool Speak( AIConcept_t &concept, AI_CriteriaSet *criteria, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL );
// Given modifiers (which are colon-delimited strings), fill out a criteria set including this
// character's contexts and the ones in the modifier. This lets us hang on to them after a call
// to SpeakFindResponse.
void GatherCriteria( AI_CriteriaSet *outputCritera, const AIConcept_t &concept, const char *modifiers );
// These two methods allow looking up a response and dispatching it to be two different steps
// AI_Response *SpeakFindResponse( AIConcept_t concept, const char *modifiers = NULL );
// AI_Response *SpeakFindResponse( AIConcept_t &concept, AI_CriteriaSet *criteria );
// Find the appropriate response for the given concept. Return false if none found.
// Fills out the response object that you provide.
bool FindResponse( AI_Response &outResponse, AIConcept_t &concept, AI_CriteriaSet *modifiers = NULL );
virtual bool SpeakDispatchResponse( AIConcept_t &concept, AI_Response *response, AI_CriteriaSet *criteria, 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 void OnSpeechFinished() {};
// This function can be overriden by games to suppress speech altogether during glue screens, etc
static bool IsSpeechGloballySuppressed();
// --------------------------------
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 );
#ifdef MAPBASE_VSCRIPT
bool ScriptSpeakRawScene( char const *soundname, float delay ) { return SpeakRawScene( soundname, delay, NULL ); }
bool ScriptSpeakAutoGeneratedScene( char const *soundname, float delay ) { return SpeakAutoGeneratedScene( soundname, delay ); }
int ScriptSpeakRawSentence( char const *pszSentence, float delay ) { return SpeakRawSentence( pszSentence, delay ); }
bool ScriptSpeak( char const *concept, const char *modifiers ) { return Speak( CAI_Concept( concept ), modifiers[0] != '\0' ? modifiers : NULL ); }
#endif
// helper used in dealing with RESPONSE_ENTITYIO
// response is the output of AI_Response::GetName
// note: the response string will get stomped on (by strtok)
// returns false on failure (eg, couldn't match parse contents)
static bool FireEntIOFromResponse( char *response, CBaseEntity *pInitiator );
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, 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:
void SetOuter( CBaseFlex *pOuter );
CBaseFlex * GetOuter() { return m_pOuter; }
const CBaseFlex * GetOuter() const { return m_pOuter; }
private:
CHandle<CBaseFlex> m_pOuter;
};
//-----------------------------------------------------------------------------
//
// 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 );
virtual bool Speak( AIConcept_t concept, AI_CriteriaSet *pCriteria, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL );
#ifdef MAPBASE
virtual bool Speak( AIConcept_t concept, AI_CriteriaSet& modifiers, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL ) { return Speak( concept, &modifiers, pszOutResponseChosen, bufsize, filter ); }
#endif
void GatherCriteria( AI_CriteriaSet *outputCritera, const AIConcept_t &concept, const char *modifiers );
// These two methods allow looking up a response and dispatching it to be two different steps
#ifdef MAPBASE
//AI_Response *SpeakFindResponse( AIConcept_t concept, const AI_CriteriaSet& modifiers );
inline bool SpeakDispatchResponse( AIConcept_t concept, AI_Response &response, AI_CriteriaSet *criteria = NULL ) { return SpeakDispatchResponse( concept, &response, criteria ); }
#endif
bool SpeakFindResponse( AI_Response& outResponse, AIConcept_t concept, const char *modifiers = NULL );
// AI_Response * SpeakFindResponse( AIConcept_t concept, const char *modifiers = NULL );
// AI_Response *SpeakFindResponse( AIConcept_t concept, AI_CriteriaSet *criteria );
// AI_Response *SpeakFindResponse( AIConcept_t concept );
// Find the appropriate response for the given concept. Return false if none found.
// Fills out the response object that you provide.
bool FindResponse( AI_Response &outResponse, AIConcept_t &concept, AI_CriteriaSet *criteria = NULL );
bool SpeakDispatchResponse( AIConcept_t concept, AI_Response *response, AI_CriteriaSet *criteria = NULL );
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 bool CAI_ExpresserHost<BASE_NPC>::Speak( AIConcept_t concept, AI_CriteriaSet *pCriteria, char *pszOutResponseChosen /*=NULL*/, size_t bufsize /* = 0 */, IRecipientFilter *filter /* = NULL */ )
{
AssertOnce( this->GetExpresser()->GetOuter() == this );
CAI_Expresser * const RESTRICT pExpresser = this->GetExpresser();
concept.SetSpeaker(this);
// add in any local criteria to the one passed on the command line.
pExpresser->GatherCriteria( pCriteria, concept, NULL );
// call the "I have aleady gathered criteria" version of Expresser::Speak
return pExpresser->Speak( concept, pCriteria, 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 void CAI_ExpresserHost<BASE_NPC>::GatherCriteria( AI_CriteriaSet *outputCriteria, const AIConcept_t &concept, const char *modifiers )
{
return this->GetExpresser()->GatherCriteria( outputCriteria, concept, modifiers );
}
#if 1
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline bool CAI_ExpresserHost<BASE_NPC>::SpeakFindResponse(AI_Response& outResponse, AIConcept_t concept, const char *modifiers /*= NULL*/ )
{
AI_CriteriaSet criteria;
GatherCriteria(&criteria, concept, modifiers);
return FindResponse( outResponse, concept, &criteria );
}
#else
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline AI_Response *CAI_ExpresserHost<BASE_NPC>::SpeakFindResponse( AIConcept_t concept, const char *modifiers /*= NULL*/ )
{
return this->GetExpresser()->SpeakFindResponse( concept, modifiers );
}
#endif
#if 0
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline AI_Response *CAI_ExpresserHost<BASE_NPC>::SpeakFindResponse( AIConcept_t concept, AI_CriteriaSet *criteria /*= NULL*/ )
{
return this->GetExpresser()->SpeakFindResponse( concept, criteria );
}
//-----------------------------------------------------------------------------
// In this case we clearly don't care to hang on to the criteria, so make a convenience
// class that generates a one off.
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline AI_Response * CAI_ExpresserHost<BASE_NPC>::SpeakFindResponse( AIConcept_t concept )
{
AI_CriteriaSet criteria;
GatherCriteria( &criteria, concept, NULL );
return this->GetExpresser()->SpeakFindResponse( concept, &criteria );
}
#endif
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline bool CAI_ExpresserHost<BASE_NPC>::FindResponse( AI_Response &outResponse, AIConcept_t &concept, AI_CriteriaSet *criteria )
{
return this->GetExpresser()->FindResponse( outResponse, concept, criteria );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline bool CAI_ExpresserHost<BASE_NPC>::SpeakDispatchResponse( AIConcept_t concept, AI_Response *response, AI_CriteriaSet *criteria )
{
if ( this->GetExpresser()->SpeakDispatchResponse( concept, response, criteria ) )
{
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 );
}
//-----------------------------------------------------------------------------
/// A shim under CAI_ExpresserHost you can use when deriving a new expresser
/// host type under CAI_BaseNPC. This does the extra step of declaring an m_pExpresser
/// member and initializing it from CreateComponents(). If your BASE_NPC class isn't
/// actually an NPC, then CreateComponents() never gets called and you won't have
/// an expresser created.
/// Note: you still need to add m_pExpresser to the Datadesc for your derived type.
/// This is because I couldn't figure out how to make a templatized datadesc declaration
/// that works generically on the template type.
template <class BASE_NPC, class EXPRESSER_TYPE>
class CAI_ExpresserHostWithData : public CAI_ExpresserHost<BASE_NPC>
{
DECLARE_CLASS_NOFRIEND( CAI_ExpresserHostWithData, CAI_ExpresserHost );
public:
CAI_ExpresserHostWithData( ) : m_pExpresser(NULL) {};
virtual CAI_Expresser *GetExpresser() { return m_pExpresser; }
const CAI_Expresser *GetExpresser() const { return m_pExpresser; }
virtual bool CreateComponents()
{
return BaseClass::CreateComponents() && ( CreateExpresser() != NULL );
}
protected:
EXPRESSER_TYPE *CreateExpresser( void )
{
AssertMsg1( m_pExpresser == NULL, "Tried to double-initialize expresser in %s\n", GetDebugName() );
m_pExpresser = new EXPRESSER_TYPE(this);
if ( !m_pExpresser)
{
AssertMsg1( false, "Creating an expresser failed in %s\n", GetDebugName() );
return NULL;
}
m_pExpresser->Connect(this);
return m_pExpresser;
}
virtual ~CAI_ExpresserHostWithData( void )
{
delete m_pExpresser;
m_pExpresser = NULL;
}
EXPRESSER_TYPE *m_pExpresser;
};
/// response rules
namespace RR
{
/// some applycontext clauses have operators preceding them,
/// like ++1 which means "take the current value and increment it
/// by one". These classes detect these cases and do the appropriate
/// thing.
class CApplyContextOperator
{
public:
inline CApplyContextOperator( int nSkipChars ) : m_nSkipChars(nSkipChars) {};
/// perform whatever this operator does upon the given context value.
/// Default op is simply to copy old to new.
/// pOldValue should be the currently set value of the context. May be NULL meaning no prior value.
/// pOperator the value that applycontext says to set
/// pNewValue a pointer to a buffer where the real new value will be writ.
/// returns true on success; false on failure (eg, tried to increment a
/// non-numeric value).
virtual bool Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize );
/// This is the function that should be called from outside,
/// fed the input string, it'll select the right operator
/// to apply.
static CApplyContextOperator *FindOperator( const char *pContextString );
protected:
int m_nSkipChars; // how many chars to "skip" in the value string to get past the op specifier to the actual value
// eg, "++3" has a m_nSkipChars of 2, because the op string "++" is two characters.
};
class CIncrementOperator : public CApplyContextOperator
{
public:
inline CIncrementOperator( int nSkipChars ) : CApplyContextOperator(nSkipChars) {};
virtual bool Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize );
};
class CDecrementOperator : public CApplyContextOperator
{
public:
inline CDecrementOperator( int nSkipChars ) : CApplyContextOperator(nSkipChars) {};
virtual bool Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize );
};
class CToggleOperator : public CApplyContextOperator
{
public:
inline CToggleOperator( int nSkipChars ) : CApplyContextOperator(nSkipChars) {};
virtual bool Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize );
};
// the singleton operators
extern CApplyContextOperator sm_OpCopy;
extern CIncrementOperator sm_OpIncrement;
extern CDecrementOperator sm_OpDecrement;
extern CToggleOperator sm_OpToggle;
};
//-----------------------------------------------------------------------------
#include "ai_speechqueue.h"
//-----------------------------------------------------------------------------
// A kind of AI Expresser that can dispatch a follow-up speech event when it
// finishes speaking.
//-----------------------------------------------------------------------------
class CAI_ExpresserWithFollowup : public CAI_Expresser
{
public:
CAI_ExpresserWithFollowup( CBaseFlex *pOuter = NULL ) : CAI_Expresser(pOuter),
m_pPostponedFollowup(NULL)
{};
virtual bool Speak( AIConcept_t &concept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL );
virtual bool SpeakDispatchResponse( AIConcept_t &concept, AI_Response *response, AI_CriteriaSet *criteria, IRecipientFilter *filter = NULL );
virtual void SpeakDispatchFollowup( AI_ResponseFollowup &followup );
virtual void OnSpeechFinished();
typedef CAI_Expresser BaseClass;
protected:
static void DispatchFollowupThroughQueue( const AIConcept_t &concept,
const char *criteriaStr,
const CResponseQueue::CFollowupTargetSpec_t &target,
float delay,
CBaseEntity * RESTRICT pOuter );
AI_ResponseFollowup *m_pPostponedFollowup; // TODO: save/restore
CResponseQueue::CFollowupTargetSpec_t m_followupTarget;
};
class CMultiplayer_Expresser : public CAI_ExpresserWithFollowup
{
public:
CMultiplayer_Expresser( CBaseFlex *pOuter = NULL );
//~CMultiplayer_Expresser();
virtual bool IsSpeaking();
void AllowMultipleScenes();
void DisallowMultipleScenes();
private:
bool m_bAllowMultipleScenes;
};
#endif // AI_SPEECH_H

View File

@ -0,0 +1,475 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "basemultiplayerplayer.h"
#include "ai_baseactor.h"
#include "ai_speech.h"
//#include "flex_expresser.h"
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
extern ConVar ai_debug_speech;
#define DebuggingSpeech() ai_debug_speech.GetBool()
extern ConVar rr_debugresponses;
ConVar rr_followup_maxdist( "rr_followup_maxdist", "1800", FCVAR_CHEAT, "'then ANY' or 'then ALL' response followups will be dispatched only to characters within this distance." );
///////////////////////////////////////////////////////////////////////////////
// RESPONSE QUEUE DATA STRUCTURE
///////////////////////////////////////////////////////////////////////////////
CResponseQueue::CResponseQueue( int queueSize ) : m_Queue(queueSize), m_ExpresserTargets(8,8)
{};
/// Add a deferred response.
void CResponseQueue::Add( const AIConcept_t &concept, ///< concept to dispatch
const AI_CriteriaSet * RESTRICT contexts,
float time, ///< when to dispatch it. You can specify a time of zero to mean "immediately."
const CFollowupTargetSpec_t &targetspec,
CBaseEntity *pIssuer
)
{
// Add a response.
AssertMsg( m_Queue.Count() < AI_RESPONSE_QUEUE_SIZE, "AI Response queue overfilled." );
QueueType_t::IndexLocalType_t idx = m_Queue.AddToTail();
m_Queue[idx].Init( concept, contexts, time, targetspec, pIssuer );
}
/// Remove a deferred response matching the concept and issuer.
void CResponseQueue::Remove( const AIConcept_t &concept, ///< concept to dispatch
CBaseEntity * const RESTRICT pIssuer ///< the entity issuing the response, if one exists.
) RESTRICT
{
// walk through the queue until we find a response matching the concept and issuer, then strike it.
QueueType_t::IndexLocalType_t idx = m_Queue.Head();
while (idx != m_Queue.InvalidIndex())
{
CDeferredResponse &response = m_Queue[idx];
QueueType_t::IndexLocalType_t previdx = idx; // advance the index immediately because we may be deleting the "current" element
idx = m_Queue.Next(idx); // is now the next index
if ( CompareConcepts( response.m_concept, concept ) && // if concepts match and
( !pIssuer || ( response.m_hIssuer.Get() == pIssuer ) ) // issuer is null, or matches the one in the response
)
{
m_Queue.Remove(previdx);
}
}
}
void CResponseQueue::RemoveSpeechQueuedFor( const CBaseEntity *pSpeaker )
{
// walk through the queue until we find a response matching the speaker, then strike it.
// because responses are dispatched from inside a loop that is already walking through the
// queue, it's not safe to actually remove the elements. Instead, quash it by replacing it
// with a null event.
for ( QueueType_t::IndexLocalType_t idx = m_Queue.Head() ;
idx != m_Queue.InvalidIndex() ;
idx = m_Queue.Next(idx) ) // is now the next index
{
CDeferredResponse &response = m_Queue[idx];
if ( response.m_Target.m_hHandle.Get() == pSpeaker )
{
response.Quash();
}
}
}
// TODO: use a more compact representation.
void CResponseQueue::DeferContextsFromCriteriaSet( DeferredContexts_t &contextsOut, const AI_CriteriaSet * RESTRICT criteriaIn )
{
contextsOut.Reset();
if (criteriaIn)
{
contextsOut.Merge(criteriaIn);
}
}
void CResponseQueue::PerFrameDispatch()
{
failsafe:
// Walk through the list, find any messages whose time has come, and dispatch them. Then remove them.
QueueType_t::IndexLocalType_t idx = m_Queue.Head();
while (idx != m_Queue.InvalidIndex())
{
// do we need to dispatch this concept?
CDeferredResponse &response = m_Queue[idx];
QueueType_t::IndexLocalType_t previdx = idx; // advance the index immediately because we may be deleting the "current" element
idx = m_Queue.Next(idx); // is now the next index
if ( response.IsQuashed() )
{
// we can delete this entry now
m_Queue.Remove(previdx);
}
else if ( response.m_fDispatchTime <= gpGlobals->curtime )
{
// dispatch. we've had bugs where dispatches removed things from inside the queue;
// so, as a failsafe, if the queue length changes as a result, start over.
int oldLength = m_Queue.Count();
DispatchOneResponse(response);
if ( m_Queue.Count() < oldLength )
{
AssertMsg( false, "Response queue length changed in non-reentrant way! FAILSAFE TRIGGERED" );
goto failsafe; // ick
}
// we can delete this entry now
m_Queue.Remove(previdx);
}
}
}
/// Add an expressor owner to this queue.
void CResponseQueue::AddExpresserHost(CBaseEntity *host)
{
EHANDLE ehost(host);
// see if it's in there already
if (m_ExpresserTargets.HasElement(ehost))
{
AssertMsg1(false, "Tried to add %s to response queue when it was already in there.", host->GetDebugName());
}
else
{
// zip through the queue front to back, first see if there's any invalid handles to replace
int count = m_ExpresserTargets.Count();
for (int i = 0 ; i < count ; ++i )
{
if ( !m_ExpresserTargets[i].Get() )
{
m_ExpresserTargets[i] = ehost;
return;
}
}
// if we're down here we didn't find one to replace, so append the host to the end.
m_ExpresserTargets.AddToTail(ehost);
}
}
/// Remove an expresser host from this queue.
void CResponseQueue::RemoveExpresserHost(CBaseEntity *host)
{
int idx = m_ExpresserTargets.Find(host);
if (idx == -1)
{
// AssertMsg1(false, "Tried to remove %s from response queue, but it's not in there to begin with!", host->GetDebugName() );
}
else
{
m_ExpresserTargets.FastRemove(idx);
}
}
/// Get the expresser for a base entity.
/// TODO: Kind of an ugly hack until I get the class hierarchy straightened out.
static CAI_Expresser *InferExpresserFromBaseEntity(CBaseEntity * RESTRICT pEnt)
{
if ( CBaseMultiplayerPlayer *pPlayer = dynamic_cast<CBaseMultiplayerPlayer *>(pEnt) )
{
return pPlayer->GetExpresser();
}
else if ( CAI_BaseActor *pActor = dynamic_cast<CAI_BaseActor *>(pEnt) )
{
return pActor->GetExpresser();
}
/*
else if ( CFlexExpresser *pFlex = dynamic_cast<CFlexExpresser *>(pEnt) )
{
return pFlex->GetExpresser();
}
*/
else
{
return NULL;
}
}
void CResponseQueue::CDeferredResponse::Quash()
{
m_Target = CFollowupTargetSpec_t();
m_fDispatchTime = 0;
}
bool CResponseQueue::DispatchOneResponse(CDeferredResponse &response)
{
// find the target.
CBaseEntity * RESTRICT pTarget = NULL;
AI_CriteriaSet &deferredCriteria = response.m_contexts;
CAI_Expresser * RESTRICT pEx = NULL;
CBaseEntity * RESTRICT pIssuer = response.m_hIssuer.Get(); // MAY BE NULL
float followupMaxDistSq;
{
/*
CFlexExpresser * RESTRICT pOrator = CFlexExpresser::AsFlexExpresser( pIssuer );
if ( pOrator )
{
// max dist is overridden. "0" means infinite distance (for orators only),
// anything else is a finite distance.
if ( pOrator->m_flThenAnyMaxDist > 0 )
{
followupMaxDistSq = pOrator->m_flThenAnyMaxDist * pOrator->m_flThenAnyMaxDist;
}
else
{
followupMaxDistSq = FLT_MAX;
}
}
else
*/
{
followupMaxDistSq = rr_followup_maxdist.GetFloat(); // square of max audibility distance
followupMaxDistSq *= followupMaxDistSq;
}
}
switch (response.m_Target.m_iTargetType)
{
case kDRT_SPECIFIC:
{
pTarget = response.m_Target.m_hHandle.Get();
}
break;
case kDRT_ANY:
{
return DispatchOneResponse_ThenANY( response, &deferredCriteria, pIssuer, followupMaxDistSq );
}
break;
case kDRT_ALL:
{
bool bSaidAnything = false;
Vector issuerLocation;
if ( pIssuer )
{
issuerLocation = pIssuer->GetAbsOrigin();
}
// find all characters
int numExprs = GetNumExpresserTargets();
for ( int i = 0 ; i < numExprs; ++i )
{
pTarget = GetExpresserHost(i);
float distIssuerToTargetSq = 0.0f;
if ( pIssuer )
{
distIssuerToTargetSq = (pTarget->GetAbsOrigin() - issuerLocation).LengthSqr();
if ( distIssuerToTargetSq > followupMaxDistSq )
continue; // too far
}
pEx = InferExpresserFromBaseEntity(pTarget);
if ( !pEx || pTarget == pIssuer )
continue;
AI_CriteriaSet characterCriteria;
pEx->GatherCriteria(&characterCriteria, response.m_concept, NULL);
characterCriteria.Merge(&deferredCriteria);
if ( pIssuer )
{
characterCriteria.AppendCriteria( "dist_from_issuer", UTIL_VarArgs( "%f", sqrt(distIssuerToTargetSq) ) );
}
AI_Response prospectiveResponse;
if ( pEx->FindResponse( prospectiveResponse, response.m_concept, &characterCriteria ) )
{
// dispatch it
bSaidAnything = pEx->SpeakDispatchResponse(response.m_concept, &prospectiveResponse, &deferredCriteria) || bSaidAnything ;
}
}
return bSaidAnything;
}
break;
default:
// WTF?
AssertMsg1( false, "Unknown deferred response type %d\n", response.m_Target.m_iTargetType );
return false;
}
if (!pTarget)
return false; // we're done right here.
// Get the expresser for the target.
pEx = InferExpresserFromBaseEntity(pTarget);
if (!pEx)
return false;
AI_CriteriaSet characterCriteria;
pEx->GatherCriteria(&characterCriteria, response.m_concept, NULL);
characterCriteria.Merge(&deferredCriteria);
pEx->Speak( response.m_concept, &characterCriteria );
return true;
}
//
ConVar rr_thenany_score_slop( "rr_thenany_score_slop", "0.0", FCVAR_CHEAT, "When computing respondents for a 'THEN ANY' rule, all rule-matching scores within this much of the best score will be considered." );
#define EXARRAYMAX 32 // maximum number of prospective expressers in the array (hardcoded for simplicity)
bool CResponseQueue::DispatchOneResponse_ThenANY( CDeferredResponse &response, AI_CriteriaSet * RESTRICT pDeferredCriteria, CBaseEntity * const RESTRICT pIssuer, float followupMaxDistSq )
{
CBaseEntity * RESTRICT pTarget = NULL;
CAI_Expresser * RESTRICT pEx = NULL;
float bestScore = 0;
float slop = rr_thenany_score_slop.GetFloat();
Vector issuerLocation;
if ( pIssuer )
{
issuerLocation = pIssuer->GetAbsOrigin();
}
// this is an array of prospective respondents.
CAI_Expresser * RESTRICT pBestEx[EXARRAYMAX];
AI_Response responseToSay[EXARRAYMAX];
int numExFound = 0; // and this is the high water mark for the array.
// Here's the algorithm: we're going to walk through all the characters, finding the
// highest scoring ones for this rule. Let the highest score be called k.
// Because there may be (n) many characters all scoring k, we store an array of
// all characters with score k, then choose randomly from that array at return.
// We also define an allowable error for k in the global cvar
// rr_thenany_score_slop , which may be zero.
// find all characters (except the issuer)
int numExprs = GetNumExpresserTargets();
AssertMsg1( numExprs <= EXARRAYMAX, "Response queue has %d possible expresser targets, please increase EXARRAYMAX ", numExprs );
for ( int i = 0 ; i < numExprs; ++i )
{
pTarget = GetExpresserHost(i);
if ( pTarget == pIssuer )
continue; // don't dispatch to myself
if ( !pTarget->IsAlive() )
continue; // dead men tell no tales
float distIssuerToTargetSq = 0.0f;
if ( pIssuer )
{
distIssuerToTargetSq = (pTarget->GetAbsOrigin() - issuerLocation).LengthSqr();
if ( distIssuerToTargetSq > followupMaxDistSq )
continue; // too far
}
pEx = InferExpresserFromBaseEntity(pTarget);
if ( !pEx )
continue;
AI_CriteriaSet characterCriteria;
pEx->GatherCriteria(&characterCriteria, response.m_concept, NULL);
characterCriteria.Merge( pDeferredCriteria );
pTarget->ModifyOrAppendDerivedCriteria( characterCriteria );
if ( pIssuer )
{
characterCriteria.AppendCriteria( "dist_from_issuer", UTIL_VarArgs( "%f", sqrt(distIssuerToTargetSq) ) );
}
AI_Response prospectiveResponse;
if ( pEx->FindResponse( prospectiveResponse, response.m_concept, &characterCriteria ) )
{
float score = prospectiveResponse.GetMatchScore();
if ( score > 0 && !prospectiveResponse.IsEmpty() ) // ignore scores that are zero, regardless of slop
{
// if this score is better than all we've seen (outside the slop), then replace the array with
// an entry just to this expresser
if ( score > bestScore + slop )
{
responseToSay[0] = prospectiveResponse;
pBestEx[0] = pEx;
bestScore = score;
numExFound = 1;
}
else if ( score >= bestScore - slop ) // if this score is at least as good as the best we've seen, but not better than all
{
if ( numExFound >= EXARRAYMAX )
continue; // SAFETY: don't overflow the array
responseToSay[numExFound] = prospectiveResponse;
pBestEx[numExFound] = pEx;
bestScore = fpmax( score, bestScore );
numExFound += 1;
}
}
}
}
// if I have a response, dispatch it.
if ( numExFound > 0 )
{
// get a random number between 0 and the responses found
int iSelect = numExFound > 1 ? RandomInt( 0, numExFound - 1 ) : 0;
if ( pBestEx[iSelect] != NULL )
{
return pBestEx[iSelect]->SpeakDispatchResponse( response.m_concept, responseToSay + iSelect, pDeferredCriteria );
}
else
{
AssertMsg( false, "Response queue somehow found a response, but no expresser for it.\n" );
return false;
}
}
else
{ // I did not find a response.
return false;
}
return false; // just in case
}
void CResponseQueue::Evacuate()
{
m_Queue.RemoveAll();
}
#undef EXARRAYMAX
///////////////////////////////////////////////////////////////////////////////
// RESPONSE QUEUE MANAGER
///////////////////////////////////////////////////////////////////////////////
void CResponseQueueManager::LevelInitPreEntity( void )
{
if (m_pQueue == NULL)
{
m_pQueue = new CResponseQueue(AI_RESPONSE_QUEUE_SIZE);
}
}
CResponseQueueManager::~CResponseQueueManager()
{
if (m_pQueue != NULL)
{
delete m_pQueue;
m_pQueue = NULL;
}
}
void CResponseQueueManager::Shutdown()
{
if (m_pQueue != NULL)
{
delete m_pQueue;
m_pQueue = NULL;
}
}
void CResponseQueueManager::FrameUpdatePostEntityThink()
{
Assert(m_pQueue);
m_pQueue->PerFrameDispatch();
}
CResponseQueueManager g_ResponseQueueManager( "CResponseQueueManager" );

View File

@ -0,0 +1,239 @@
//========= 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

View File

@ -66,6 +66,9 @@
#include "mapbase/matchers.h"
#include "mapbase/datadesc_mod.h"
#endif
#ifdef NEW_RESPONSE_SYSTEM
#include "ai_speech.h"
#endif
#if defined( TF_DLL )
#include "tf_gamerules.h"
@ -7653,7 +7656,11 @@ bool CBaseEntity::HasContext( const char *nameandvalue ) const
const char *p = nameandvalue;
while ( p )
{
#ifdef NEW_RESPONSE_SYSTEM
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL, nameandvalue );
#else
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL );
#endif
return HasContext( key, value );
}
@ -7700,7 +7707,11 @@ void CBaseEntity::RemoveContext( const char *contextName )
while ( p )
{
duration = 0.0f;
#ifdef NEW_RESPONSE_SYSTEM
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration, contextName );
#else
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration );
#endif
if ( duration )
{
duration += gpGlobals->curtime;
@ -8938,56 +8949,76 @@ void CBaseEntity::AddContext( const char *contextName )
{
char key[ 128 ];
char value[ 128 ];
float duration;
float duration = 0.0f;
const char *p = contextName;
while ( p )
{
duration = 0.0f;
#ifdef NEW_RESPONSE_SYSTEM
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration, contextName );
#else
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration );
#endif
if ( duration )
{
duration += gpGlobals->curtime;
}
int iIndex = FindContextByName( key );
if ( iIndex != -1 )
{
// Set the existing context to the new value
m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( value );
m_ResponseContexts[iIndex].m_fExpirationTime = duration;
continue;
}
ResponseContext_t newContext;
newContext.m_iszName = AllocPooledString( key );
newContext.m_iszValue = AllocPooledString( value );
newContext.m_fExpirationTime = duration;
m_ResponseContexts.AddToTail( newContext );
AddContext( key, value, duration );
}
}
#ifdef MAPBASE
void CBaseEntity::AddContext( const char *name, const char *value, float duration )
{
int iIndex = FindContextByName( name );
if ( iIndex != -1 )
{
// Set the existing context to the new value
#ifdef NEW_RESPONSE_SYSTEM
char buf[64];
if ( RR::CApplyContextOperator::FindOperator( value )->Apply(
m_ResponseContexts[iIndex].m_iszValue.ToCStr(), value, buf, sizeof(buf) ) )
{
m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( buf );
}
else
{
Warning( "RR: could not apply operator %s to prior value %s\n",
value, m_ResponseContexts[iIndex].m_iszValue.ToCStr() );
m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( value );
}
#else
m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( value );
m_ResponseContexts[iIndex].m_fExpirationTime = duration;
return;
}
ResponseContext_t newContext;
newContext.m_iszName = AllocPooledString( name );
newContext.m_iszValue = AllocPooledString( value );
newContext.m_fExpirationTime = duration;
m_ResponseContexts.AddToTail( newContext );
}
#endif
m_ResponseContexts[iIndex].m_fExpirationTime = duration;
}
else
{
ResponseContext_t newContext;
newContext.m_iszName = AllocPooledString( name );
#ifdef NEW_RESPONSE_SYSTEM
char buf[64];
if ( RR::CApplyContextOperator::FindOperator( value )->Apply(
NULL, value, buf, sizeof(buf) ) )
{
newContext.m_iszValue = AllocPooledString( buf );
}
else
{
newContext.m_iszValue = AllocPooledString( value );
}
#else
newContext.m_iszValue = AllocPooledString( value );
#endif
newContext.m_fExpirationTime = duration;
m_ResponseContexts.AddToTail( newContext );
}
}
//-----------------------------------------------------------------------------
// Purpose:
@ -9139,6 +9170,11 @@ void CBaseEntity::InputChangeVariable( inputdata_t &inputdata )
//-----------------------------------------------------------------------------
void CBaseEntity::DispatchResponse( const char *conceptName )
{
#ifdef NEW_RESPONSE_SYSTEM
#undef IResponseSystem
using namespace ResponseRules;
#endif
IResponseSystem *rs = GetResponseSystem();
if ( !rs )
return;
@ -9169,6 +9205,56 @@ void CBaseEntity::DispatchResponse( const char *conceptName )
// Handle the response here...
char response[ 256 ];
result.GetResponse( response, sizeof( response ) );
#ifdef NEW_RESPONSE_SYSTEM
switch (result.GetType())
{
case ResponseRules::RESPONSE_SPEAK:
{
EmitSound(response);
}
break;
case ResponseRules::RESPONSE_SENTENCE:
{
int sentenceIndex = SENTENCEG_Lookup(response);
if (sentenceIndex == -1)
{
// sentence not found
break;
}
// FIXME: Get pitch from npc?
CPASAttenuationFilter filter(this);
CBaseEntity::EmitSentenceByIndex(filter, entindex(), CHAN_VOICE, sentenceIndex, 1, result.GetSoundLevel(), 0, PITCH_NORM);
}
break;
case ResponseRules::RESPONSE_SCENE:
{
// Try to fire scene w/o an actor
InstancedScriptedScene(NULL, response);
}
break;
case ResponseRules::RESPONSE_PRINT:
{
}
break;
case ResponseRules::RESPONSE_ENTITYIO:
{
CAI_Expresser::FireEntIOFromResponse(response, this);
break;
}
#ifdef MAPBASE
case ResponseRules::RESPONSE_VSCRIPT:
{
RunScript( response, "ResponseScript" );
break;
}
#endif
default:
// Don't know how to handle .vcds!!!
break;
}
#else
#ifdef MAPBASE
if (response[0] == '$')
{
@ -9263,6 +9349,7 @@ void CBaseEntity::DispatchResponse( const char *conceptName )
// Don't know how to handle .vcds!!!
break;
}
#endif
}
//-----------------------------------------------------------------------------

View File

@ -20,6 +20,10 @@
#include "ServerNetworkProperty.h"
#include "shareddefs.h"
#include "engine/ivmodelinfo.h"
#ifdef NEW_RESPONSE_SYSTEM
#include "AI_Criteria.h"
#include "AI_ResponseSystem.h"
#endif
#include "vscript/ivscript.h"
#include "vscript_server.h"
@ -29,8 +33,10 @@ class CDmgAccumulator;
struct CSoundParameters;
#ifndef NEW_RESPONSE_SYSTEM
class AI_CriteriaSet;
class IResponseSystem;
#endif
class IEntitySaveUtils;
class CRecipientFilter;
class CStudioHdr;
@ -39,6 +45,11 @@ class CStudioHdr;
// FIXME: Could do this in the script file by making it required and bumping up weighting there instead...
#define CONCEPT_WEIGHT 5.0f
#ifdef NEW_RESPONSE_SYSTEM
// Relax the namespace standard a bit so that less code has to be changed
#define IResponseSystem ResponseRules::IResponseSystem
#endif
typedef CHandle<CBaseEntity> EHANDLE;
#define MANUALMODE_GETSET_PROP(type, accessorName, varName) \
@ -1001,10 +1012,10 @@ public:
const char *GetContextValue( const char *contextName ) const;
float GetContextExpireTime( const char *name );
void RemoveContext( const char *nameandvalue );
void AddContext( const char *name, const char *value, float duration = 0.0f );
#endif
void AddContext( const char *nameandvalue );
void AddContext( const char *name, const char *value, float duration = 0.0f );
protected:
CUtlVector< ResponseContext_t > m_ResponseContexts;
@ -1294,6 +1305,12 @@ public:
#endif
virtual void ModifyOrAppendCriteria( AI_CriteriaSet& set );
#ifdef NEW_RESPONSE_SYSTEM
// this computes criteria that depend on the other criteria having been set.
// needs to be done in a second pass because we may have multiple overrids for
// a context before it all settles out.
virtual void ModifyOrAppendDerivedCriteria( AI_CriteriaSet& set ) {};
#endif
void AppendContextToCriteria( AI_CriteriaSet& set, const char *prefix = "" );
#ifdef MAPBASE
void ReAppendContextCriteria( AI_CriteriaSet& set );

View File

@ -19,7 +19,9 @@
struct flexsettinghdr_t;
struct flexsetting_t;
#ifndef NEW_RESPONSE_SYSTEM
class AI_Response;
#endif
//-----------------------------------------------------------------------------
// Purpose: A .vfe referenced by a scene during .vcd playback

View File

@ -28,6 +28,7 @@ CBaseMultiplayerPlayer::CBaseMultiplayerPlayer()
CBaseMultiplayerPlayer::~CBaseMultiplayerPlayer()
{
m_pAchievementKV->deleteThis();
delete m_pExpresser;
}
//-----------------------------------------------------------------------------
@ -87,10 +88,16 @@ IResponseSystem *CBaseMultiplayerPlayer::GetResponseSystem()
//-----------------------------------------------------------------------------
// Purpose: Doesn't actually speak the concept. Just finds a response in the system. You then have to play it yourself.
//-----------------------------------------------------------------------------
AI_Response *CBaseMultiplayerPlayer::SpeakConcept( int iConcept )
bool CBaseMultiplayerPlayer::SpeakConcept( AI_Response &response, int iConcept )
{
m_iCurrentConcept = iConcept;
return SpeakFindResponse( g_pszMPConcepts[iConcept] );
#ifdef NEW_RESPONSE_SYSTEM
CAI_Concept concept(g_pszMPConcepts[iConcept]);
concept.SetSpeaker(this);
return FindResponse( response, concept );
#else
return SpeakFindResponse( response, g_pszMPConcepts[iConcept] );
#endif
}
//-----------------------------------------------------------------------------

View File

@ -28,7 +28,7 @@ public:
virtual bool SpeakIfAllowed( AIConcept_t concept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL );
virtual IResponseSystem *GetResponseSystem();
AI_Response *SpeakConcept( int iConcept );
bool SpeakConcept( AI_Response &response, int iConcept );
virtual bool SpeakConceptIfAllowed( int iConcept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL );
virtual bool CanHearAndReadChatFrom( CBasePlayer *pPlayer );

View File

@ -1755,7 +1755,11 @@ void CAI_ActBusyBehavior::PlaySoundForActBusy( busyanimparts_t AnimPart )
CAI_Expresser *pExpresser = GetOuter()->GetExpresser();
if ( pExpresser )
{
#ifdef NEW_RESPONSE_SYSTEM
CAI_Concept concept = STRING(pBusyAnim->iszSounds[AnimPart]);
#else
const char *concept = STRING(pBusyAnim->iszSounds[AnimPart]);
#endif
// Must be able to speak the concept
if ( !pExpresser->IsSpeaking() && pExpresser->CanSpeakConcept( concept ) )

View File

@ -143,7 +143,12 @@ void CAI_PolicingBehavior::HostSpeakSentence( const char *pSentence, SentencePri
}
else if ( GetOuter()->GetExpresser() )
{
#ifdef NEW_RESPONSE_SYSTEM
CAI_Concept concept = pSentence;
GetOuter()->GetExpresser()->Speak( concept );
#else
GetOuter()->GetExpresser()->Speak( pSentence );
#endif
}
#endif
}
@ -168,7 +173,12 @@ void CAI_PolicingBehavior::HostSpeakSentence( const char *pSentence, const char
}
else if ( GetOuter()->GetExpresser() )
{
#ifdef NEW_RESPONSE_SYSTEM
CAI_Concept concept( pSentence );
GetOuter()->GetExpresser()->Speak( concept, modifiers );
#else
GetOuter()->GetExpresser()->Speak( pSentence, modifiers );
#endif
}
#endif
}

View File

@ -243,6 +243,58 @@ void CSpeaker::DispatchResponse( const char *conceptName )
PrecacheScriptSound( response );
}
#ifdef NEW_RESPONSE_SYSTEM
switch (result.GetType())
{
case ResponseRules::RESPONSE_SPEAK:
{
pTarget->EmitSound( response );
}
break;
case ResponseRules::RESPONSE_SENTENCE:
{
int sentenceIndex = SENTENCEG_Lookup( response );
if (sentenceIndex == -1)
{
// sentence not found
break;
}
// FIXME: Get pitch from npc?
CPASAttenuationFilter filter( pTarget );
CBaseEntity::EmitSentenceByIndex( filter, pTarget->entindex(), CHAN_VOICE, sentenceIndex, 1, result.GetSoundLevel(), 0, PITCH_NORM );
}
break;
case ResponseRules::RESPONSE_SCENE:
{
CBaseFlex *pFlex = NULL;
if (pTarget != this)
{
// Attempt to get flex on the target
pFlex = dynamic_cast<CBaseFlex*>(pTarget);
}
InstancedScriptedScene(pFlex, response);
}
break;
case ResponseRules::RESPONSE_PRINT:
{
}
break;
case ResponseRules::RESPONSE_ENTITYIO:
{
CAI_Expresser::FireEntIOFromResponse( response, pTarget );
break;
}
case ResponseRules::RESPONSE_VSCRIPT:
{
pTarget->RunScript( response, "ResponseScript" );
break;
}
default:
break;
}
#else
switch ( result.GetType() )
{
case RESPONSE_SPEAK:
@ -283,6 +335,7 @@ void CSpeaker::DispatchResponse( const char *conceptName )
default:
break;
}
#endif
// AllocPooledString?
m_OnSpeak.Set(MAKE_STRING(response), pTarget, this);

View File

@ -3004,7 +3004,11 @@ bool CNPC_Combine::SpeakIfAllowed( const char *concept, const char *modifiers, S
AI_CriteriaSet set;
if (modifiers)
{
#ifdef NEW_RESPONSE_SYSTEM
GatherCriteria( &set, concept, modifiers );
#else
GetExpresser()->MergeModifiers(set, modifiers);
#endif
}
return SpeakIfAllowed( concept, set, sentencepriority, sentencecriteria );
}

View File

@ -1156,7 +1156,11 @@ bool CNPC_MetroPolice::SpeakIfAllowed( const char *concept, const char *modifier
AI_CriteriaSet set;
if (modifiers)
{
#ifdef NEW_RESPONSE_SYSTEM
GatherCriteria( &set, concept, modifiers );
#else
GetExpresser()->MergeModifiers(set, modifiers);
#endif
}
return SpeakIfAllowed( concept, set, sentencepriority, sentencecriteria );
}

View File

@ -1210,16 +1210,28 @@ const char *CZombieCustom::GetMoanSound( int nSound )
// We could probably do this through the response system alone now, but whatever.
modifiers.AppendCriteria( "moansound", UTIL_VarArgs("%i", nSound & 4) );
#ifdef NEW_RESPONSE_SYSTEM
AI_Response response;
CAI_Concept concept = "TLK_ZOMBIE_MOAN";
concept.SetSpeaker( this );
if (!FindResponse( response, concept, &modifiers ))
return "NPC_BaseZombie.Moan1";
#else
AI_Response *response = SpeakFindResponse(TLK_ZOMBIE_MOAN, modifiers);
if ( !response )
return "NPC_BaseZombie.Moan1";
#endif
// Must be static so it could be returned
static char szSound[128];
#ifdef NEW_RESPONSE_SYSTEM
response.GetName(szSound, sizeof(szSound));
#else
response->GetName(szSound, sizeof(szSound));
delete response;
#endif
return szSound;
}

View File

@ -32,6 +32,7 @@
#include "scenefilecache/ISceneFileCache.h"
#include "SceneCache.h"
#include "scripted.h"
#include "basemultiplayerplayer.h"
#include "env_debughistory.h"
#include "team.h"
#include "triggers.h"
@ -2348,6 +2349,40 @@ void CSceneEntity::InputInterjectResponse( inputdata_t &inputdata )
}
else
{
#ifdef NEW_RESPONSE_SYSTEM
CUtlString modifiers("scene:");
modifiers += STRING( GetEntityName() );
while (candidates.Count() > 0)
{
// Pick a random slot in the candidates array.
int slot = RandomInt( 0, candidates.Count() - 1 );
CAI_BaseActor *npc = candidates[ slot ];
// Try to find the response for this slot.
AI_Response response;
CAI_Concept concept(inputdata.value.String());
concept.SetSpeaker(npc);
AI_CriteriaSet set;
npc->GatherCriteria(&set, concept, modifiers.Get());
bool result = npc->FindResponse( response, concept, &set);
if ( result )
{
float duration = npc->GetResponseDuration( &response );
if ( ( duration > 0.0f ) && npc->PermitResponse( duration ) )
{
// If we could look it up, dispatch it and bail.
npc->SpeakDispatchResponse( concept, &response, &set);
return;
}
}
// Remove this entry and look for another one.
candidates.FastRemove(slot);
}
#else
CUtlVector< NPCInterjection > validResponses;
char modifiers[ 512 ];
@ -2399,6 +2434,7 @@ void CSceneEntity::InputInterjectResponse( inputdata_t &inputdata )
}
}
}
#endif
}
}
@ -3019,6 +3055,16 @@ void CSceneEntity::QueueResumePlayback( void )
CAI_BaseActor *pBaseActor = dynamic_cast<CAI_BaseActor*>(pActor);
if ( pBaseActor )
{
#ifdef NEW_RESPONSE_SYSTEM
AI_Response response;
CAI_Concept concept(STRING(m_iszResumeSceneFile));
bool result = pBaseActor->FindResponse( response, concept, NULL );
if ( result )
{
const char* szResponse = response.GetResponsePtr();
bStartedScene = InstancedScriptedScene( NULL, szResponse, &m_hWaitingForThisResumeScene, 0, false ) != 0;
}
#else
AI_Response *result = pBaseActor->SpeakFindResponse( STRING(m_iszResumeSceneFile), NULL );
if ( result )
{
@ -3026,6 +3072,7 @@ void CSceneEntity::QueueResumePlayback( void )
result->GetResponse( response, sizeof( response ) );
bStartedScene = InstancedScriptedScene( NULL, response, &m_hWaitingForThisResumeScene, 0, false ) != 0;
}
#endif
}
}
}
@ -4783,6 +4830,26 @@ void CSceneEntity::OnSceneFinished( bool canceled, bool fireoutput )
m_OnCompletion.FireOutput( this, this, 0 );
}
#ifdef NEW_RESPONSE_SYSTEM
{
CBaseFlex *pFlex = FindNamedActor( 0 ) ;
if ( pFlex )
{
CBaseMultiplayerPlayer *pAsPlayer = dynamic_cast<CBaseMultiplayerPlayer *>(pFlex);
if (pAsPlayer)
{
CAI_Expresser *pExpresser = pAsPlayer->GetExpresser();
pExpresser->OnSpeechFinished();
}
else if ( CAI_BaseActor *pActor = dynamic_cast<CAI_BaseActor*>( pFlex ) )
{
CAI_Expresser *pExpresser = pActor->GetExpresser();
pExpresser->OnSpeechFinished();
}
}
}
#endif
// Put face back in neutral pose
ClearSceneEvents( m_pScene, canceled );
@ -5160,6 +5227,34 @@ float GetSceneDuration( char const *pszScene )
return (float)msecs * 0.001f;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pszScene -
// Output : float
//-----------------------------------------------------------------------------
float GetSceneSpeechDuration( char const* pszScene )
{
float flSecs = 0.0f;
CChoreoScene* pScene = CSceneEntity::LoadScene( pszScene, nullptr );
if (pScene)
{
for (int i = pScene->GetNumEvents() - 1; i >= 0; i--)
{
CChoreoEvent* pEvent = pScene->GetEvent( i );
if (pEvent->GetType() == CChoreoEvent::SPEAK)
{
flSecs = pEvent->GetStartTime() + pEvent->GetDuration();
break;
}
}
delete pScene;
}
return flSecs;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pszScene -

View File

@ -13,7 +13,9 @@
// List of the last 5 lines of speech from NPCs for bug reports
#define SPEECH_LIST_MAX_SOUNDS 5
#ifndef NEW_RESPONSE_SYSTEM
class AI_Response;
#endif
struct recentNPCSpeech_t
{
@ -44,6 +46,7 @@ bool IsRunningScriptedSceneWithFlexAndNotPaused( CBaseFlex *pActor, bool bIgnore
CUtlVector< CHandle< CSceneEntity > > *GetActiveSceneList();
#endif
float GetSceneDuration( char const *pszScene );
float GetSceneSpeechDuration( char const* pszScene );
int GetSceneSpeechCount( char const *pszScene );
bool IsInInterruptableScenes( CBaseFlex *pActor );

View File

@ -126,8 +126,10 @@ $Project
$File "ai_concommands.cpp"
$File "ai_condition.cpp"
$File "ai_condition.h"
$File "AI_Criteria.cpp"
$File "AI_Criteria.cpp" [!$NEW_RESPONSE_SYSTEM]
$File "AI_Criteria.h"
$File "$SRCDIR\game\shared\ai_criteria_new.cpp" [$NEW_RESPONSE_SYSTEM]
$File "$SRCDIR\game\shared\ai_criteria_new.h" [$NEW_RESPONSE_SYSTEM]
$File "ai_debug.h"
$File "$SRCDIR\game\shared\ai_debug_shared.h"
$File "ai_default.cpp"
@ -182,8 +184,10 @@ $Project
$File "ai_planesolver.h"
$File "ai_playerally.cpp"
$File "ai_playerally.h"
$File "AI_ResponseSystem.cpp"
$File "AI_ResponseSystem.cpp" [!$NEW_RESPONSE_SYSTEM]
$File "AI_ResponseSystem.h"
$File "$SRCDIR\game\shared\ai_responsesystem_new.cpp" [$NEW_RESPONSE_SYSTEM]
$File "$SRCDIR\game\shared\ai_responsesystem_new.h" [$NEW_RESPONSE_SYSTEM]
$File "ai_route.cpp"
$File "ai_route.h"
$File "ai_routedist.h"
@ -197,8 +201,10 @@ $Project
$File "ai_senses.h"
$File "ai_sentence.cpp"
$File "ai_sentence.h"
$File "ai_speech.cpp"
$File "ai_speech.cpp" [!$NEW_RESPONSE_SYSTEM]
$File "ai_speech.h"
$File "ai_speech_new.cpp" [$NEW_RESPONSE_SYSTEM]
$File "ai_speech_new.h" [$NEW_RESPONSE_SYSTEM]
$File "ai_speechfilter.cpp"
$File "ai_speechfilter.h"
$File "ai_squad.cpp"
@ -428,7 +434,6 @@ $Project
$File "init_factory.h"
$File "intermission.cpp"
$File "$SRCDIR\public\interpolatortypes.h"
$File "$SRCDIR\game\shared\interval.h"
$File "$SRCDIR\public\iregistry.h"
$File "$SRCDIR\game\shared\iscenetokenprocessor.h"
$File "iservervehicle.h"
@ -686,7 +691,6 @@ $Project
"h_export.cpp" \
"init_factory.cpp" \
"$SRCDIR\public\interpolatortypes.cpp" \
"$SRCDIR\game\shared\interval.cpp" \
"$SRCDIR\public\keyframe\keyframe.cpp" \
"$SRCDIR\common\language.cpp" \
"$SRCDIR\public\map_utils.cpp" \
@ -1000,6 +1004,7 @@ $Project
$File "$SRCDIR\public\winlite.h"
$File "$SRCDIR\public\worldsize.h"
$File "$SRCDIR\public\zip_uncompressed.h"
$File "$SRCDIR\public\tier1\interval.h"
$File "$SRCDIR\game\shared\mp_shareddefs.h"
$File "$SRCDIR\game\shared\econ\ihasowner.h"
//Haptics

View File

@ -10,6 +10,7 @@ $Configuration
{
$PreprocessorDefinitions "$BASE;ASW_PROJECTED_TEXTURES;DYNAMIC_RTT_SHADOWS;GLOWS_ENABLE"
$PreprocessorDefinitions "$BASE;MAPBASE_VSCRIPT" [$MAPBASE_VSCRIPT]
$PreprocessorDefinitions "$BASE;NEW_RESPONSE_SYSTEM" [$NEW_RESPONSE_SYSTEM]
}
}
@ -27,6 +28,9 @@ $Project
$File "postprocesscontroller.h"
$File "env_dof_controller.cpp"
$File "env_dof_controller.h"
$File "ai_expresserfollowup.cpp" [$NEW_RESPONSE_SYSTEM]
$File "ai_speechqueue.cpp" [$NEW_RESPONSE_SYSTEM]
$File "ai_speechqueue.h" [$NEW_RESPONSE_SYSTEM]
$Folder "Mapbase"
{
@ -104,5 +108,6 @@ $Project
$Folder "Link Libraries"
{
$Lib "vscript" [$MAPBASE_VSCRIPT]
$Lib "responserules" [$NEW_RESPONSE_SYSTEM]
}
}

View File

@ -0,0 +1,38 @@
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $NoKeywords: $
//
//===========================================================================//
#include "cbase.h"
#include "ai_criteria.h"
#ifdef GAME_DLL
#include "ai_speech.h"
#endif
#include <keyvalues.h>
#include "engine/ienginesound.h"
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
BEGIN_SIMPLE_DATADESC( AI_ResponseParams )
DEFINE_FIELD( flags, FIELD_SHORT ),
DEFINE_FIELD( odds, FIELD_SHORT ),
DEFINE_FIELD( soundlevel, FIELD_CHARACTER ),
DEFINE_FIELD( delay, FIELD_INTEGER ), // These are compressed down to two float16s, so treat as an INT for saverestore
DEFINE_FIELD( respeakdelay, FIELD_INTEGER ), //
END_DATADESC()
BEGIN_SIMPLE_DATADESC( AI_Response )
DEFINE_FIELD( m_Type, FIELD_CHARACTER ),
DEFINE_ARRAY( m_szResponseName, FIELD_CHARACTER, AI_Response::MAX_RESPONSE_NAME ),
DEFINE_ARRAY( m_szMatchingRule, FIELD_CHARACTER, AI_Response::MAX_RULE_NAME ),
// DEFINE_FIELD( m_pCriteria, FIELD_??? ), // Don't need to save this probably
DEFINE_EMBEDDED( m_Params ),
END_DATADESC()

View File

@ -0,0 +1,41 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef AI_CRITERIA_H
#define AI_CRITERIA_H
#ifdef _WIN32
#pragma once
#endif
#include "tier1/utlrbtree.h"
#include "tier1/utlsymbol.h"
#include "tier1/interval.h"
#include "mathlib/compressed_vector.h"
#include "../../public/responserules/response_types.h"
using ResponseRules::ResponseType_t;
extern const char *SplitContext( const char *raw, char *key, int keylen, char *value, int valuelen, float *duration, const char *entireContext );
#ifndef AI_CriteriaSet
#define AI_CriteriaSet ResponseRules::CriteriaSet
#endif
typedef ResponseRules::ResponseParams AI_ResponseParams ;
typedef ResponseRules::CRR_Response AI_Response;
/*
// An AI response that is dynamically new'ed up and returned from SpeakFindResponse.
class AI_ResponseReturnValue : AI_Response
{
};
*/
#endif // AI_CRITERIA_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef AI_RESPONSESYSTEM_H
#define AI_RESPONSESYSTEM_H
#include "utlvector.h"
#ifdef _WIN32
#pragma once
#endif
#include "ai_criteria.h"
#include "../../public/responserules/response_types.h"
// using ResponseRules::IResponseFilter;
// using ResponseRules::IResponseSystem;
ResponseRules::IResponseSystem *PrecacheCustomResponseSystem( const char *scriptfile );
ResponseRules::IResponseSystem *BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore );
void DestroyCustomResponseSystems();
class ISaveRestoreBlockHandler *GetDefaultResponseSystemSaveRestoreBlockHandler();
class ISaveRestoreOps *GetResponseSystemSaveRestoreOps();
#endif // AI_RESPONSESYSTEM_H

View File

@ -0,0 +1,28 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "ai_speechconcept.h"
#ifdef GAME_DLL
#include "game.h"
#include "ai_basenpc.h"
#include "sceneentity.h"
#endif
#include "engine/ienginesound.h"
#include "keyvalues.h"
#include "ai_criteria.h"
#include "isaverestore.h"
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
// empty

View File

@ -0,0 +1,45 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Class data for an AI Concept, an atom of response-driven dialog.
//
// $NoKeywords: $
//=============================================================================//
#ifndef AI_SPEECHCONCEPT_H
#define AI_SPEECHCONCEPT_H
#if defined( _WIN32 )
#pragma once
#endif
#include "../../public/responserules/response_types.h"
class CAI_Concept : public ResponseRules::CRR_Concept
{
public:
CAI_Concept() {};
// construct concept from a string.
CAI_Concept(const char *fromString) : CRR_Concept(fromString) {} ;
// get/set BS
inline EHANDLE GetSpeaker() const { return m_hSpeaker; }
inline void SetSpeaker(EHANDLE val) { m_hSpeaker = val; }
/*
inline EHANDLE GetTarget() const { return m_hTarget; }
inline void SetTarget(EHANDLE val) { m_hTarget = val; }
inline EHANDLE GetTopic() const { return m_hTopic; }
inline void SetTopic(EHANDLE val) { m_hTopic = val; }
*/
protected:
EHANDLE m_hSpeaker;
/*
EHANDLE m_hTarget;
EHANDLE m_hTopic;
*/
};
#endif

View File

@ -313,6 +313,12 @@ struct datamap_t
static datamap_t *GetBaseMap(); \
template <typename T> friend void DataMapAccess(T *, datamap_t **p); \
template <typename T> friend datamap_t *DataMapInit(T *);
#define DECLARE_SIMPLE_DATADESC_INSIDE_NAMESPACE() \
static datamap_t m_DataMap; \
static datamap_t *GetBaseMap(); \
template <typename T> friend void ::DataMapAccess(T *, datamap_t **p); \
template <typename T> friend datamap_t *::DataMapInit(T *);
#define DECLARE_DATADESC() \
DECLARE_SIMPLE_DATADESC() \
@ -414,6 +420,8 @@ inline void DataMapAccess(T *ignored, datamap_t **p)
*p = &T::m_DataMap;
}
template <typename T> datamap_t* DataMapInit(T*);
//-----------------------------------------------------------------------------
class CDatadescGeneratedNameHolder

View File

@ -0,0 +1,66 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Core types for the response rules -- criteria, responses, rules, and matchers.
//
// $NoKeywords: $
//=============================================================================//
#ifndef RESPONSE_HOST_INTERFACE_H
#define RESPONSE_HOST_INTERFACE_H
#ifdef _WIN32
#pragma once
#endif
#include "filesystem.h"
class IUniformRandomStream;
class ICommandLine;
namespace ResponseRules
{
// FUNCTIONS YOU MUST IMPLEMENT IN THE HOST EXECUTABLE:
// These are functions that are mentioned in the header, but need their bodies implemented
// in the .dll that links against this lib.
// This is to wrap functions that previously came from the engine interface
// back when the response rules were inside the server.dll . Now that the rules
// are included into a standalone editor, we don't necessarily have an engine around,
// so there needs to be some other implementation.
abstract_class IEngineEmulator
{
public:
/// Given an input text buffer data pointer, parses a single token into the variable token and returns the new
/// reading position
virtual const char *ParseFile( const char *data, char *token, int maxlen ) = 0;
#ifdef MAPBASE
/// (Optional) Same as ParseFile, but with casing preserved and escaped quotes supported
virtual const char *ParseFilePreserve( const char *data, char *token, int maxlen ) { return ParseFile( data, token, maxlen ); }
#endif
/// Return a pointer to an IFileSystem we can use to read and process scripts.
virtual IFileSystem *GetFilesystem() = 0;
/// Return a pointer to an instance of an IUniformRandomStream
virtual IUniformRandomStream *GetRandomStream() = 0 ;
/// Return a pointer to a tier0 ICommandLine
virtual ICommandLine *GetCommandLine() = 0;
/// Emulates the server's UTIL_LoadFileForMe
virtual byte *LoadFileForMe( const char *filename, int *pLength ) = 0;
/// Emulates the server's UTIL_FreeFile
virtual void FreeFile( byte *buffer ) = 0;
/// Somewhere in the host executable you should define this symbol and
/// point it at a singleton instance.
static IEngineEmulator *s_pSingleton;
// this is just a function that returns the pointer above -- just in
// case we need to define it differently. And I get asserts this way.
static IEngineEmulator *Get();
};
};
#endif

View File

@ -0,0 +1,458 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Core types for the response rules -- criteria, responses, rules, and matchers.
//
// $NoKeywords: $
//=============================================================================//
#ifndef RESPONSE_TYPES_H
#define RESPONSE_TYPES_H
#ifdef _WIN32
#pragma once
#endif
#include "tier1/utlrbtree.h"
#include "tier1/utlsymbol.h"
#include "tier1/interval.h"
#include "mathlib/compressed_vector.h"
#include "datamap.h"
#include "soundflags.h"
#include "tier1/utlsymbol.h"
namespace ResponseRules
{
/// Custom symbol table for the response rules.
extern CUtlSymbolTable g_RS;
};
#ifdef _MANAGED
// forward declare some editor types just so we can friend them.
namespace ResponseRulesCLI
{
ref class ResponseQueryResult;
}
#endif
namespace ResponseRules
{
using ::DataMapAccess;
// using ::DataMapInit;
class CResponseSystem;
#pragma pack(push,1)
template<typename T>
struct response_interval_t
{
T start;
T range;
interval_t &ToInterval( interval_t &dest ) const { dest.start = start; dest.range = range; return dest; }
void FromInterval( const interval_t &from ) { start = from.start; range = from.range; }
float Random() const { interval_t temp = { start, range }; return RandomInterval( temp ); }
};
typedef response_interval_t<float16_with_assign> responseparams_interval_t;
#pragma pack(pop)
#pragma pack(push,1)
struct AI_ResponseFollowup
{
// TODO: make less wasteful of memory, by using a symbol table.
const char *followup_concept; // 12 -- next response
const char *followup_contexts; // 16
float followup_delay; // 20
const char *followup_target; // 24 -- to whom is this despatched?
// AIConceptHandle_t hConcept;
const char *followup_entityiotarget; //< if this rule involves firing entity io
const char *followup_entityioinput; //< if this rule involves firing entity io
float followup_entityiodelay;
bool bFired;
inline bool IsValid( void ) const { return (followup_concept && followup_contexts); }
inline void Invalidate() { followup_concept = NULL; followup_contexts = NULL; }
inline void SetFired( bool fired ) { bFired = fired; }
inline bool HasBeenFired() { return bFired; }
AI_ResponseFollowup( void ) : followup_concept(NULL), followup_contexts(NULL), followup_delay(0), followup_target(NULL), followup_entityiotarget(NULL), followup_entityioinput(NULL), followup_entityiodelay(0), bFired(false)
{};
AI_ResponseFollowup( char *_followup_concept, char *_followup_contexts, float _followup_delay, char *_followup_target,
char *_followup_entityiotarget, char *_followup_entityioinput, float _followup_entityiodelay ) :
followup_concept(_followup_concept), followup_contexts(_followup_contexts), followup_delay(_followup_delay), followup_target(_followup_target),
followup_entityiotarget(_followup_entityiotarget), followup_entityioinput(_followup_entityioinput), followup_entityiodelay(_followup_entityiodelay),
bFired(false)
{};
};
#pragma pack(pop)
enum ResponseType_t
{
RESPONSE_NONE = 0,
RESPONSE_SPEAK,
RESPONSE_SENTENCE,
RESPONSE_SCENE,
RESPONSE_RESPONSE, // A reference to another response by name
RESPONSE_PRINT,
RESPONSE_ENTITYIO, // poke an input on an entity
#ifdef MAPBASE
RESPONSE_VSCRIPT, // Run VScript code
#endif
NUM_RESPONSES,
};
#ifdef MAPBASE
// The "apply to world" context option has been replaced with a flag-based integer which can apply contexts to more things.
//
// New ones should be implemented in:
// CResponseSystem::BuildDispatchTables() - AI_ResponseSystem.cpp (with their own funcs for m_RuleDispatch)
// CRR_Response::Describe() - rr_response.cpp
// CAI_Expresser::SpeakDispatchResponse() - ai_speech.cpp
enum
{
APPLYCONTEXT_SELF = (1 << 0), // Included for contexts that apply to both self and something else
APPLYCONTEXT_WORLD = (1 << 1), // Apply to world
APPLYCONTEXT_SQUAD = (1 << 2), // Apply to squad
APPLYCONTEXT_ENEMY = (1 << 3), // Apply to enemy
};
#endif
#pragma pack(push,1)
struct ResponseParams
{
DECLARE_SIMPLE_DATADESC_INSIDE_NAMESPACE();
enum
{
RG_DELAYAFTERSPEAK = (1<<0),
RG_SPEAKONCE = (1<<1),
RG_ODDS = (1<<2),
RG_RESPEAKDELAY = (1<<3),
RG_SOUNDLEVEL = (1<<4),
RG_DONT_USE_SCENE = (1<<5),
RG_STOP_ON_NONIDLE = (1<<6),
RG_WEAPONDELAY = (1<<7),
RG_DELAYBEFORESPEAK = (1<<8),
};
ResponseParams()
{
flags = 0;
odds = 100;
delay.start = 0;
delay.range = 0;
respeakdelay.start = 0;
respeakdelay.range = 0;
weapondelay.start = 0;
weapondelay.range = 0;
soundlevel = 0;
predelay.start = 0;
predelay.range = 0;
}
responseparams_interval_t delay; //4
responseparams_interval_t respeakdelay; //8
responseparams_interval_t weapondelay; //12
short odds; //14
short flags; //16
byte soundlevel; //17
responseparams_interval_t predelay; //21
ALIGN32 AI_ResponseFollowup *m_pFollowup;
};
#pragma pack(pop)
class CriteriaSet
{
public:
typedef CUtlSymbol CritSymbol_t; ///< just to make it clear that some symbols come out of our special static table
public:
CriteriaSet();
CriteriaSet( const CriteriaSet& src );
CriteriaSet( const char *criteria, const char *value ) ; // construct initialized with a key/value pair (convenience)
~CriteriaSet();
static CritSymbol_t ComputeCriteriaSymbol( const char *criteria );
void AppendCriteria( CritSymbol_t criteria, const char *value = "", float weight = 1.0f );
void AppendCriteria( const char *criteria, const char *value = "", float weight = 1.0f );
void AppendCriteria( const char *criteria, float value, float weight = 1.0f );
void RemoveCriteria( const char *criteria );
void Describe() const;
int GetCount() const;
int FindCriterionIndex( CritSymbol_t criteria ) const;
int FindCriterionIndex( const char *name ) const;
inline bool IsValidIndex( int index ) const;
CritSymbol_t GetNameSymbol( int nIndex ) const;
inline static const char *SymbolToStr( const CritSymbol_t &symbol );
const char *GetName( int index ) const;
const char *GetValue( int index ) const;
float GetWeight( int index ) const;
/// Merge another CriteriaSet into this one.
void Merge( const CriteriaSet *otherCriteria );
void Merge( const char *modifiers ); // add criteria parsed from a text string
/// add all of the contexts herein onto an entity. all durations are infinite.
void WriteToEntity( CBaseEntity *pEntity );
// Accessors to things that need only be done under unusual circumstances.
inline void EnsureCapacity( int num );
void Reset(); // clear out this criteria (should not be necessary)
/// When this is true, calls to AppendCriteria on a criteria that already exists
/// will override the existing value. (This is the default behavior). Can be temporarily
/// set false to prevent such overrides.
inline void OverrideOnAppend( bool bOverride ) { m_bOverrideOnAppend = bOverride; }
// For iteration from beginning to end (also should not be necessary except in
// save/load)
inline int Head() const;
inline int Next( int i ) const; // use with IsValidIndex above
const static char kAPPLYTOWORLDPREFIX = '$';
/// A last minute l4d2 change: deferred contexts prefixed with a '$'
/// character are actually applied to the world. This matches the
/// related hack in CBaseEntity::AppplyContext.
/// This function works IN-PLACE on the "from" parameter.
/// any $-prefixed criteria in pFrom become prefixed by "world",
/// and are also written into pSetOnWorld.
/// *IF* a response matches using the modified criteria, then and only
/// then should you write back the criteria in pSetOnWorld to the world
/// entity, subsequent to the match but BEFORE the dispatch.
/// Returns the number of contexts modified. If it returns 0, then
/// pSetOnWorld is empty.
static int InterceptWorldSetContexts( CriteriaSet * RESTRICT pFrom,
CriteriaSet * RESTRICT pSetOnWorld );
private:
void RemoveCriteria( int idx, bool bTestForPrefix );
struct CritEntry_t
{
CritEntry_t() :
criterianame( UTL_INVAL_SYMBOL ),
weight( 0.0f )
{
value[ 0 ] = 0;
}
CritEntry_t( const CritEntry_t& src )
{
criterianame = src.criterianame;
value[ 0 ] = 0;
weight = src.weight;
SetValue( src.value );
}
CritEntry_t& operator=( const CritEntry_t& src )
{
if ( this == &src )
return *this;
criterianame = src.criterianame;
weight = src.weight;
SetValue( src.value );
return *this;
}
static bool LessFunc( const CritEntry_t& lhs, const CritEntry_t& rhs )
{
return lhs.criterianame < rhs.criterianame;
}
void SetValue( char const *str )
{
if ( !str )
{
value[ 0 ] = 0;
}
else
{
Q_strncpy( value, str, sizeof( value ) );
}
}
CritSymbol_t criterianame;
char value[ 64 ];
float weight;
};
static CUtlSymbolTable sm_CriteriaSymbols;
typedef CUtlRBTree< CritEntry_t, short > Dict_t;
Dict_t m_Lookup;
int m_nNumPrefixedContexts; // number of contexts prefixed with kAPPLYTOWORLDPREFIX
bool m_bOverrideOnAppend;
};
inline void CriteriaSet::EnsureCapacity( int num )
{
m_Lookup.EnsureCapacity(num);
}
//-----------------------------------------------------------------------------
// Purpose: Generic container for a response to a match to a criteria set
// This is what searching for a response returns
//-----------------------------------------------------------------------------
class CRR_Response
{
public:
DECLARE_SIMPLE_DATADESC_INSIDE_NAMESPACE();
CRR_Response();
CRR_Response( const CRR_Response &from );
CRR_Response &operator=( const CRR_Response &from );
~CRR_Response();
private:
void operator delete(void* p); // please do not new or delete CRR_Responses.
public:
// void Release(); // we no longer encourage new and delete on these things
void GetName( char *buf, size_t buflen ) const;
void GetResponse( char *buf, size_t buflen ) const;
const char* GetNamePtr() const;
const char* GetResponsePtr() const;
const ResponseParams *GetParams() const { return &m_Params; }
ResponseType_t GetType() const { return (ResponseType_t)m_Type; }
soundlevel_t GetSoundLevel() const;
float GetRespeakDelay() const;
float GetWeaponDelay() const;
bool GetSpeakOnce() const;
bool ShouldntUseScene( ) const;
bool ShouldBreakOnNonIdle( void ) const;
int GetOdds() const;
float GetDelay() const;
float GetPreDelay() const;
inline bool IsEmpty() const; // true iff my response name is empty
void Invalidate() ; // wipe out my contents, mark me invalid
// Get/set the contexts we apply to character and world after execution
void SetContext( const char *context );
const char * GetContext( void ) const { return m_szContext; }
// Get/set the score I matched with (under certain circumstances)
inline float GetMatchScore( void ) { return m_fMatchScore; }
inline void SetMatchScore( float f ) { m_fMatchScore = f; }
#ifdef MAPBASE
int GetContextFlags() { return m_iContextFlags; }
bool IsApplyContextToWorld( void ) { return (m_iContextFlags & APPLYCONTEXT_WORLD) != 0; }
#else
bool IsApplyContextToWorld( void ) { return m_bApplyContextToWorld; }
#endif
void Describe( const CriteriaSet *pDebugCriteria = NULL );
void Init( ResponseType_t type,
const char *responseName,
const ResponseParams& responseparams,
const char *matchingRule,
const char *applyContext,
bool bApplyContextToWorld );
#ifdef MAPBASE
void Init( ResponseType_t type,
const char *responseName,
const ResponseParams& responseparams,
const char *matchingRule,
const char *applyContext,
int iContextFlags );
#endif
static const char *DescribeResponse( ResponseType_t type );
enum
{
MAX_RESPONSE_NAME = 64,
MAX_RULE_NAME = 64
};
private:
byte m_Type;
char m_szResponseName[ MAX_RESPONSE_NAME ];
char m_szMatchingRule[ MAX_RULE_NAME ];
ResponseParams m_Params;
float m_fMatchScore; // when instantiated dynamically in SpeakFindResponse, the score of the rule that matched it.
char * m_szContext; // context data we apply to character after running
#ifdef MAPBASE
int m_iContextFlags;
#else
bool m_bApplyContextToWorld;
#endif
#ifdef _MANAGED
friend ref class ResponseRulesCLI::ResponseQueryResult;
#endif
};
abstract_class IResponseFilter
{
public:
virtual bool IsValidResponse( ResponseType_t type, const char *pszValue ) = 0;
};
abstract_class IResponseSystem
{
public:
virtual ~IResponseSystem() {}
virtual bool FindBestResponse( const CriteriaSet& set, CRR_Response& response, IResponseFilter *pFilter = NULL ) = 0;
virtual void GetAllResponses( CUtlVector<CRR_Response> *pResponses ) = 0;
virtual void PrecacheResponses( bool bEnable ) = 0;
};
// INLINE FUNCTIONS
// Used as a failsafe in finding responses.
bool CRR_Response::IsEmpty() const
{
return m_szResponseName[0] == 0;
}
inline bool CriteriaSet::IsValidIndex( int index ) const
{
return ( index >= 0 && index < ((int)(m_Lookup.Count())) );
}
inline int CriteriaSet::Head() const
{
return m_Lookup.FirstInorder();
}
inline int CriteriaSet::Next( int i ) const
{
return m_Lookup.NextInorder(i);
}
inline const char *CriteriaSet::SymbolToStr( const CritSymbol_t &symbol )
{
return sm_CriteriaSymbols.String(symbol);
}
}
#include "rr_speechconcept.h"
#include "response_host_interface.h"
#endif

View File

@ -0,0 +1,57 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Class data for an AI Concept, an atom of response-driven dialog.
//
// $NoKeywords: $
//=============================================================================//
#ifndef RR_SPEECHCONCEPT_H
#define RR_SPEECHCONCEPT_H
#if defined( _WIN32 )
#pragma once
#endif
#include "utlsymbol.h"
#define RR_CONCEPTS_ARE_STRINGS 0
typedef CUtlSymbolTable CRR_ConceptSymbolTable;
namespace ResponseRules
{
class CRR_Concept
{
public: // local typedefs
typedef CUtlSymbol tGenericId; // an int-like type that can be used to refer to all concepts of this type
tGenericId m_iConcept;
public:
CRR_Concept() {};
// construct concept from a string.
CRR_Concept(const char *fromString);
// Return as a string
const char *GetStringConcept() const;
static const char *GetStringForGenericId(tGenericId genericId);
operator tGenericId() const { return m_iConcept; }
operator const char *() const { return GetStringConcept(); }
inline bool operator==(const CRR_Concept &other) // default is compare by concept ids
{
return m_iConcept == other.m_iConcept;
}
bool operator==(const char *pszConcept);
protected:
private:
// dupe a concept
// CRR_Concept& operator=(CRR_Concept &other);
CRR_Concept& operator=(const char *fromString);
};
};
#endif

View File

@ -131,6 +131,70 @@ T Max( T const &val1, T const &val2 )
#define TRUE (!FALSE)
#endif
//-----------------------------------------------------------------------------
// fsel
//-----------------------------------------------------------------------------
#ifndef _X360
#define fsel(c,x,y) ( (c) >= 0 ? (x) : (y) )
// integer conditional move
// if a >= 0, return x, else y
#define isel(a,x,y) ( ((a) >= 0) ? (x) : (y) )
// if x = y, return a, else b
#define ieqsel(x,y,a,b) (( (x) == (y) ) ? (a) : (b))
// if the nth bit of a is set (counting with 0 = LSB),
// return x, else y
// this is fast if nbit is a compile-time immediate
#define ibitsel(a, nbit, x, y) ( ( ((a) & (1 << (nbit))) != 0 ) ? (x) : (y) )
#else
// __fsel(double fComparand, double fValGE, double fLT) == fComparand >= 0 ? fValGE : fLT
// this is much faster than if ( aFloat > 0 ) { x = .. }
// the XDK defines two intrinsics, one for floats and one for doubles -- it's the same
// opcode, but the __fself version tells the compiler not to do a wasteful unnecessary
// rounding op after each sel.
// #define fsel __fsel
FORCEINLINE double fsel(double fComparand, double fValGE, double fLT) { return __fsel( fComparand, fValGE, fLT ); }
FORCEINLINE float fsel(float fComparand, float fValGE, float fLT) { return __fself( fComparand, fValGE, fLT ); }
// if a >= 0, return x, else y
FORCEINLINE int isel( int a, int x, int y )
{
int mask = a >> 31; // arithmetic shift right, splat out the sign bit
return x + ((y - x) & mask);
};
// if a >= 0, return x, else y
FORCEINLINE unsigned isel( int a, unsigned x, unsigned y )
{
int mask = a >> 31; // arithmetic shift right, splat out the sign bit
return x + ((y - x) & mask);
};
// ( x == y ) ? a : b
FORCEINLINE unsigned ieqsel( unsigned x, unsigned y, unsigned a, unsigned b )
{
unsigned mask = (x == y) ? 0 : -1;
return a + ((b - a) & mask);
};
// ( x == y ) ? a : b
FORCEINLINE int ieqsel( int x, int y, int a, int b )
{
int mask = (x == y) ? 0 : -1;
return a + ((b - a) & mask);
};
// if the nth bit of a is set (counting with 0 = LSB),
// return x, else y
// this is fast if nbit is a compile-time immediate
#define ibitsel(a, nbit, x, y) ( (x) + (((y) - (x)) & (((a) & (1 << (nbit))) ? 0 : -1)) )
#endif
#ifndef DONT_DEFINE_BOOL // Needed for Cocoa stuff to compile.
typedef int BOOL;

View File

@ -704,29 +704,6 @@ typedef uint32 HMODULE;
typedef void *HANDLE;
#endif
//-----------------------------------------------------------------------------
// fsel
//-----------------------------------------------------------------------------
#ifndef _X360
static FORCEINLINE float fsel(float fComparand, float fValGE, float fLT)
{
return fComparand >= 0 ? fValGE : fLT;
}
static FORCEINLINE double fsel(double fComparand, double fValGE, double fLT)
{
return fComparand >= 0 ? fValGE : fLT;
}
#else
// __fsel(double fComparand, double fValGE, double fLT) == fComparand >= 0 ? fValGE : fLT
// this is much faster than if ( aFloat > 0 ) { x = .. }
#define fsel __fsel
#endif
//-----------------------------------------------------------------------------
// FP exception handling
//-----------------------------------------------------------------------------

View File

@ -0,0 +1,477 @@
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $NoKeywords: $
//
//===========================================================================//
#include "rrbase.h"
#include "utlmap.h"
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
using namespace ResponseRules;
//-----------------------------------------------------------------------------
// Case-insensitive criteria symbol table
//-----------------------------------------------------------------------------
CUtlSymbolTable CriteriaSet::sm_CriteriaSymbols( 1024, 1024, true );
//-----------------------------------------------------------------------------
// Purpose:
// Input : *raw -
// *key -
// keylen -
// *value -
// valuelen -
// *duration -
// Output : static bool
//-----------------------------------------------------------------------------
const char *SplitContext( const char *raw, char *key, int keylen, char *value, int valuelen, float *duration, const char *entireContext )
{
char *colon1 = Q_strstr( raw, ":" );
if ( !colon1 )
{
DevMsg( "SplitContext: warning, ignoring context '%s', missing colon separator!\n", raw );
*key = *value = 0;
return NULL;
}
int len = colon1 - raw;
Q_strncpy( key, raw, MIN( len + 1, keylen ) );
key[ MIN( len, keylen - 1 ) ] = 0;
bool last = false;
char *end = Q_strstr( colon1 + 1, "," );
if ( !end )
{
int remaining = Q_strlen( colon1 + 1 );
end = colon1 + 1 + remaining;
last = true;
}
char *colon2 = Q_strstr( colon1 + 1, ":" );
if ( colon2 && ( colon2 < end ) )
{
if ( duration )
*duration = atof( colon2 + 1 );
char durationStartChar = *(colon2 + 1);
if ( durationStartChar < '0' || durationStartChar > '9' )
{
DevMsg( "SplitContext: warning, ignoring context '%s', missing comma separator! Entire context was '%s'.\n", raw, entireContext );
*key = *value = 0;
return NULL;
}
len = MIN( colon2 - ( colon1 + 1 ), valuelen - 1 );
Q_strncpy( value, colon1 + 1, len + 1 );
value[ len ] = 0;
}
else
{
if ( duration )
*duration = 0.0;
len = MIN( end - ( colon1 + 1 ), valuelen - 1 );
Q_strncpy( value, colon1 + 1, len + 1 );
value[ len ] = 0;
}
return last ? NULL : end + 1;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CriteriaSet::CriteriaSet() : m_Lookup( 0, 0, CritEntry_t::LessFunc ), m_bOverrideOnAppend(true),
m_nNumPrefixedContexts(0)
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CriteriaSet::CriteriaSet( const CriteriaSet& src ) : m_Lookup( 0, 0, CritEntry_t::LessFunc ), m_nNumPrefixedContexts(src.m_nNumPrefixedContexts)
{
m_Lookup.EnsureCapacity( src.m_Lookup.Count() );
for ( short i = src.m_Lookup.FirstInorder();
i != src.m_Lookup.InvalidIndex();
i = src.m_Lookup.NextInorder( i ) )
{
m_Lookup.Insert( src.m_Lookup[ i ] );
}
}
CriteriaSet::CriteriaSet( const char *criteria, const char *value ) : m_Lookup( 0, 0, CritEntry_t::LessFunc ), m_bOverrideOnAppend(true)
{
AppendCriteria(criteria,value);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CriteriaSet::~CriteriaSet()
{
}
//-----------------------------------------------------------------------------
// Computes a symbol for the criteria
//-----------------------------------------------------------------------------
CriteriaSet::CritSymbol_t CriteriaSet::ComputeCriteriaSymbol( const char *criteria )
{
return sm_CriteriaSymbols.AddString( criteria );
}
//-----------------------------------------------------------------------------
// Computes a symbol for the criteria
//-----------------------------------------------------------------------------
void CriteriaSet::AppendCriteria( CriteriaSet::CritSymbol_t criteria, const char *value, float weight )
{
int idx = FindCriterionIndex( criteria );
if ( idx == -1 )
{
CritEntry_t entry;
entry.criterianame = criteria;
MEM_ALLOC_CREDIT();
idx = m_Lookup.Insert( entry );
if ( sm_CriteriaSymbols.String(criteria)[0] == kAPPLYTOWORLDPREFIX )
{
m_nNumPrefixedContexts += 1;
}
}
else // criteria already existed
{
// bail out if override existing criteria is not allowed
if ( !m_bOverrideOnAppend )
return;
}
CritEntry_t *entry = &m_Lookup[ idx ];
entry->SetValue( value );
entry->weight = weight;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *criteria -
// "" -
// 1.0f -
//-----------------------------------------------------------------------------
void CriteriaSet::AppendCriteria( const char *pCriteriaName, const char *value /*= ""*/, float weight /*= 1.0f*/ )
{
CUtlSymbol criteria = ComputeCriteriaSymbol( pCriteriaName );
AppendCriteria( criteria, value, weight );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *criteria -
// "" -
// 1.0f -
//-----------------------------------------------------------------------------
void CriteriaSet::AppendCriteria( const char *criteria, float value, float weight /*= 1.0f*/ )
{
char buf[32];
V_snprintf( buf, 32, "%f", value );
AppendCriteria( criteria, buf, weight );
}
//-----------------------------------------------------------------------------
// Removes criteria in a set
//-----------------------------------------------------------------------------
void CriteriaSet::RemoveCriteria( const char *criteria )
{
const int idx = FindCriterionIndex( criteria );
if ( idx == -1 )
return;
if ( criteria[0] == kAPPLYTOWORLDPREFIX )
{
Assert( m_nNumPrefixedContexts > 0 );
m_nNumPrefixedContexts = isel( m_nNumPrefixedContexts - 1, m_nNumPrefixedContexts - 1, 0 );
}
RemoveCriteria( idx, false );
}
// bTestForIndex tells us whether the calling function has already checked for a
// $ prefix and decremented m_nNumPrefixedContexts appropriately (false),
// or if this function should do that (true).
void CriteriaSet::RemoveCriteria( int idx, bool bTestForPrefix )
{
Assert( m_Lookup.IsValidIndex(idx) );
if ( bTestForPrefix )
{
if ( sm_CriteriaSymbols.String( m_Lookup[idx].criterianame )[0] == kAPPLYTOWORLDPREFIX )
{
Assert( m_nNumPrefixedContexts > 0 );
m_nNumPrefixedContexts = isel( m_nNumPrefixedContexts - 1, m_nNumPrefixedContexts - 1, 0 );
}
}
m_Lookup.RemoveAt( idx );
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : int
//-----------------------------------------------------------------------------
int CriteriaSet::GetCount() const
{
return m_Lookup.Count();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *name -
// Output : int
//-----------------------------------------------------------------------------
int CriteriaSet::FindCriterionIndex( CritSymbol_t criteria ) const
{
CritEntry_t search;
search.criterianame = criteria;
int idx = m_Lookup.Find( search );
return ( idx == m_Lookup.InvalidIndex() ) ? -1 : idx;
}
int CriteriaSet::FindCriterionIndex( const char *name ) const
{
CUtlSymbol criteria = ComputeCriteriaSymbol( name );
return FindCriterionIndex( criteria );
}
//-----------------------------------------------------------------------------
// Returns the name symbol
//-----------------------------------------------------------------------------
CriteriaSet::CritSymbol_t CriteriaSet::GetNameSymbol( int nIndex ) const
{
if ( nIndex < 0 || nIndex >= (int)m_Lookup.Count() )
return UTL_INVAL_SYMBOL;
const CritEntry_t *entry = &m_Lookup[ nIndex ];
return entry->criterianame;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : index -
// Output : char const
//-----------------------------------------------------------------------------
const char *CriteriaSet::GetName( int index ) const
{
if ( index < 0 || index >= (int)m_Lookup.Count() )
return "";
else
{
const char *pCriteriaName = sm_CriteriaSymbols.String( m_Lookup[ index ].criterianame );
return pCriteriaName ? pCriteriaName : "";
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : index -
// Output : char const
//-----------------------------------------------------------------------------
const char *CriteriaSet::GetValue( int index ) const
{
if ( index < 0 || index >= (int)m_Lookup.Count() )
return "";
const CritEntry_t *entry = &m_Lookup[ index ];
return entry->value ? entry->value : "";
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : index -
// Output : float
//-----------------------------------------------------------------------------
float CriteriaSet::GetWeight( int index ) const
{
if ( index < 0 || index >= (int)m_Lookup.Count() )
return 1.0f;
const CritEntry_t *entry = &m_Lookup[ index ];
return entry->weight;
}
//-----------------------------------------------------------------------------
// Purpose: Merge another criteria set into this one.
//-----------------------------------------------------------------------------
void CriteriaSet::Merge( const CriteriaSet * RESTRICT otherCriteria )
{
Assert(otherCriteria);
if (!otherCriteria)
return;
// for now, just duplicate everything.
int count = otherCriteria->GetCount();
EnsureCapacity( count + GetCount() );
for ( int i = 0 ; i < count ; ++i )
{
AppendCriteria( otherCriteria->GetNameSymbol(i), otherCriteria->GetValue(i), otherCriteria->GetWeight(i) );
}
}
void CriteriaSet::Merge( const char *modifiers ) // add criteria parsed from a text string
{
// Always include any optional modifiers
if ( modifiers == NULL )
return;
char copy_modifiers[ 255 ];
const char *pCopy;
char key[ 128 ] = { 0 };
char value[ 128 ] = { 0 };
Q_strncpy( copy_modifiers, modifiers, sizeof( copy_modifiers ) );
pCopy = copy_modifiers;
while( pCopy )
{
pCopy = SplitContext( pCopy, key, sizeof( key ), value, sizeof( value ), NULL, modifiers );
if( *key && *value )
{
AppendCriteria( key, value, 1 );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CriteriaSet::Describe() const
{
// build an alphabetized representation of the set for printing
typedef CUtlMap<const char *, const CritEntry_t *> tMap;
tMap m_TempMap( 0, m_Lookup.Count(), CaselessStringLessThan );
for ( short i = m_Lookup.FirstInorder(); i != m_Lookup.InvalidIndex(); i = m_Lookup.NextInorder( i ) )
{
const CritEntry_t *entry = &m_Lookup[ i ];
m_TempMap.Insert( sm_CriteriaSymbols.String( entry->criterianame ), entry );
}
for ( tMap::IndexType_t i = m_TempMap.FirstInorder(); i != m_TempMap.InvalidIndex(); i = m_TempMap.NextInorder( i ) )
{
// const CritEntry_t *entry = &m_TempMap[ i ];
// const char *pCriteriaName = sm_CriteriaSymbols.String( entry->criterianame );
const char *name = m_TempMap.Key( i );
const CritEntry_t *entry = m_TempMap.Element( i );
if ( entry->weight != 1.0f )
{
DevMsg( " %20s = '%s' (weight %f)\n", name, entry->value ? entry->value : "", entry->weight );
}
else
{
DevMsg( " %20s = '%s'\n", name, entry->value ? entry->value : "" );
}
}
/*
for ( short i = m_Lookup.FirstInorder(); i != m_Lookup.InvalidIndex(); i = m_Lookup.NextInorder( i ) )
{
const CritEntry_t *entry = &m_Lookup[ i ];
const char *pCriteriaName = sm_CriteriaSymbols.String( entry->criterianame );
if ( entry->weight != 1.0f )
{
DevMsg( " %20s = '%s' (weight %f)\n", pCriteriaName, entry->value ? entry->value : "", entry->weight );
}
else
{
DevMsg( " %20s = '%s'\n", pCriteriaName, entry->value ? entry->value : "" );
}
}
*/
}
void CriteriaSet::Reset()
{
m_Lookup.Purge();
}
void CriteriaSet::WriteToEntity( CBaseEntity *pEntity )
{
#if 0
if ( GetCount() < 1 )
return;
for ( int i = Head() ; IsValidIndex(i); i = Next(i) )
{
pEntity->AddContext( GetName(i), GetValue(i), 0 );
}
#else
AssertMsg( false, "CriteriaSet::WriteToEntity has not been ported from l4d2.\n" );
#endif
}
int CriteriaSet::InterceptWorldSetContexts( CriteriaSet * RESTRICT pFrom, CriteriaSet * RESTRICT pSetOnWorld )
{
// Assert( pFrom ); Assert( pTo ); Assert( pSetOnWorld );
Assert( pSetOnWorld != pFrom );
Assert( pSetOnWorld->GetCount() == 0 );
if ( pFrom->m_nNumPrefixedContexts == 0 )
{
// nothing needs to be done to it.
return 0;
}
#ifdef DEBUG
// save this off for later error checking.
const int nPrefixedContexts = pFrom->m_nNumPrefixedContexts;
#endif
// make enough space for the expected output quantity.
pSetOnWorld->EnsureCapacity( pFrom->m_nNumPrefixedContexts );
// initialize a buffer with the "world" prefix (so we can use strncpy instead of snprintf and be much faster)
char buf[80] = { 'w', 'o', 'r', 'l', 'd', '\0' };
const unsigned int PREFIXLEN = 5; // strlen("world")
// create a second tree that has the appropriately renamed criteria,
// then swap it into pFrom
CriteriaSet rewrite;
rewrite.EnsureCapacity( pFrom->GetCount() + 1 );
for ( int i = pFrom->Head(); pFrom->IsValidIndex(i); i = pFrom->Next(i) )
{
const char *pszName = pFrom->GetName( i );
if ( pszName[0] == CriteriaSet::kAPPLYTOWORLDPREFIX )
{ // redirect to the world contexts
V_strncpy( buf+PREFIXLEN, pszName+1, sizeof(buf) - PREFIXLEN );
rewrite.AppendCriteria( buf, pFrom->GetValue(i), pFrom->GetWeight(i) );
pSetOnWorld->AppendCriteria( pszName+1, pFrom->GetValue(i), pFrom->GetWeight(i) );
buf[PREFIXLEN] = 0;
}
else
{ // does not need to be fiddled; do not write back to world
rewrite.AppendCriteria( pFrom->GetNameSymbol(i), pFrom->GetValue(i), pFrom->GetWeight(i) );
}
}
AssertMsg2( pSetOnWorld->GetCount() == nPrefixedContexts, "Count of $ persistent RR contexts is inconsistent (%d vs %d)! Call Elan.",
pSetOnWorld->GetCount(), nPrefixedContexts );
pFrom->m_nNumPrefixedContexts = 0;
pFrom->m_Lookup.Swap(rewrite.m_Lookup);
return pSetOnWorld->GetCount();
}

View File

@ -0,0 +1,41 @@
//-----------------------------------------------------------------------------
// response_rules.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$macro SRCDIR "..\.."
$include "$SRCDIR\vpc_scripts\source_lib_base.vpc"
$Configuration
{
$Compiler
{
$AdditionalIncludeDirectories "$BASE;..\public\responserules"
$PreprocessorDefinitions "$BASE;RR_RUNTIME"
}
}
$Project "responserules_runtime"
{
$Folder "Source Files"
{
$File "criteriaset.cpp"
$File "response_system.cpp"
$File "response_system.h"
$File "response_types.cpp"
$File "response_types_internal.cpp"
$File "response_types_internal.h"
$File "rr_convars.cpp"
$File "rr_response.cpp"
$File "rr_speechconcept.cpp"
$File "rrrlib.cpp"
}
$Folder "Public Header Files"
{
$File "$SRCDIR\public\responserules\response_host_interface.h"
$File "$SRCDIR\public\responserules\response_types.h"
$File "$SRCDIR\public\responserules\rr_speechconcept.h"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,316 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: The CResponseSystem class. Don't include this header; include the response_types
// into which it is transcluded.
//
// $NoKeywords: $
//=============================================================================//
#ifndef RESPONSE_SYSTEM_H
#define RESPONSE_SYSTEM_H
#ifdef _WIN32
#pragma once
#endif
#include "utldict.h"
namespace ResponseRules
{
typedef ResponseParams AI_ResponseParams ;
#define AI_CriteriaSet ResponseRules::CriteriaSet
//-----------------------------------------------------------------------------
// Purpose: The database of all available responses.
// The Rules are partitioned based on a variety of factors (presently,
// speaker and concept) for faster lookup, basically a seperate-chained hash.
//-----------------------------------------------------------------------------
class CResponseSystem : public IResponseSystem
{
public:
CResponseSystem();
~CResponseSystem();
typedef void (CResponseSystem::*pfnResponseDispatch)( void );
typedef void (CResponseSystem::*pfnParseRuleDispatch)( Rule & );
typedef void (CResponseSystem::*pfnParseResponseDispatch)( ParserResponse &, ResponseGroup&, AI_ResponseParams * );
typedef void (CResponseSystem::*pfnParseResponseGroupDispatch) ( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams );
typedef CUtlMap< unsigned,pfnResponseDispatch > DispatchMap_t;
typedef CUtlMap< unsigned,pfnParseRuleDispatch > ParseRuleDispatchMap_t;
typedef CUtlMap< unsigned,pfnParseResponseDispatch > ParseResponseDispatchMap_t;
typedef CUtlMap< unsigned,pfnParseResponseGroupDispatch > ParseResponseGroupDispatchMap_t;
#pragma region IResponseSystem
// IResponseSystem
virtual bool FindBestResponse( const CriteriaSet& set, CRR_Response& response, IResponseFilter *pFilter = NULL );
virtual void GetAllResponses( CUtlVector<CRR_Response> *pResponses );
#pragma endregion Implement interface from IResponseSystem
virtual void Release() = 0;
virtual void DumpRules();
bool IsCustomManagable() { return m_bCustomManagable; }
void Clear();
void DumpDictionary( const char *pszName );
protected:
void BuildDispatchTables();
bool Dispatch( char const *pToken, unsigned int uiHash, DispatchMap_t &rMap );
bool DispatchParseRule( char const *pToken, unsigned int uiHash, ParseRuleDispatchMap_t &rMap, Rule &newRule );
bool DispatchParseResponse( char const *pToken, unsigned int uiHash, ParseResponseDispatchMap_t &rMap, ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp );
bool DispatchParseResponseGroup( char const *pToken, unsigned int uiHash, ParseResponseGroupDispatchMap_t &rMap, char const *responseGroupName, ResponseGroup& newGroup, AI_ResponseParams &groupResponseParams );
virtual const char *GetScriptFile( void ) = 0;
void LoadRuleSet( const char *setname );
void ResetResponseGroups();
float LookForCriteria( const CriteriaSet &criteriaSet, int iCriteria );
float RecursiveLookForCriteria( const CriteriaSet &criteriaSet, Criteria *pParent );
public:
void CopyRuleFrom( Rule *pSrcRule, ResponseRulePartition::tIndex iRule, CResponseSystem *pCustomSystem );
void CopyCriteriaFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem );
void CopyResponsesFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem );
void CopyEnumerationsFrom( CResponseSystem *pCustomSystem );
//private:
struct Enumeration
{
float value;
};
struct ResponseSearchResult
{
ResponseSearchResult()
{
group = NULL;
action = NULL;
}
ResponseGroup *group;
ParserResponse *action;
};
inline bool ParseToken( void )
{
if ( m_bUnget )
{
m_bUnget = false;
return true;
}
if ( m_ScriptStack.Count() <= 0 )
{
Assert( 0 );
return false;
}
m_ScriptStack[ 0 ].currenttoken = IEngineEmulator::Get()->ParseFile( m_ScriptStack[ 0 ].currenttoken, token, sizeof( token ) );
m_ScriptStack[ 0 ].tokencount++;
return m_ScriptStack[ 0 ].currenttoken != NULL ? true : false;
}
#ifdef MAPBASE
inline bool ParseTokenIntact( void )
{
if ( m_bUnget )
{
m_bUnget = false;
return true;
}
if ( m_ScriptStack.Count() <= 0 )
{
Assert( 0 );
return false;
}
m_ScriptStack[ 0 ].currenttoken = IEngineEmulator::Get()->ParseFilePreserve( m_ScriptStack[ 0 ].currenttoken, token, sizeof( token ) );
m_ScriptStack[ 0 ].tokencount++;
return m_ScriptStack[ 0 ].currenttoken != NULL ? true : false;
}
#endif
inline void Unget()
{
m_bUnget = true;
}
inline bool TokenWaiting( void )
{
if ( m_ScriptStack.Count() <= 0 )
{
Assert( 0 );
return false;
}
const char *p = m_ScriptStack[ 0 ].currenttoken;
if ( !p )
{
Error( "AI_ResponseSystem: Unxpected TokenWaiting() with NULL buffer in %s", (char * ) m_ScriptStack[ 0 ].name );
return false;
}
while ( *p && *p!='\n')
{
// Special handler for // comment blocks
if ( *p == '/' && *(p+1) == '/' )
return false;
if ( !V_isspace( *p ) || isalnum( *p ) )
return true;
p++;
}
return false;
}
void ParseOneResponse( const char *responseGroupName, ResponseGroup& group, ResponseParams *defaultParams = NULL );
void ParseInclude( void );
void ParseResponse( void );
void ParseCriterion( void );
void ParseRule( void );
void ParseEnumeration( void );
private:
void ParseRule_MatchOnce( Rule &newRule );
void ParseRule_ApplyContextToWorld( Rule &newRule );
#ifdef MAPBASE
void ParseRule_ApplyContextToSquad( Rule &newRule );
void ParseRule_ApplyContextToEnemy( Rule &newRule );
#endif
void ParseRule_ApplyContext( Rule &newRule );
void ParseRule_Response( Rule &newRule );
//void ParseRule_ForceWeight( Rule &newRule );
void ParseRule_Criteria( Rule &newRule );
char const *m_pParseRuleName;
bool m_bParseRuleValid;
void ParseResponse_Weight( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp );
void ParseResponse_PreDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp );
void ParseResponse_NoDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp );
void ParseResponse_DefaultDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp );
void ParseResponse_Delay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp );
void ParseResponse_SpeakOnce( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp );
void ParseResponse_NoScene( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp );
void ParseResponse_StopOnNonIdle( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp );
void ParseResponse_Odds( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp );
void ParseResponse_RespeakDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp );
void ParseResponse_WeaponDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp );
void ParseResponse_Soundlevel( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp );
void ParseResponse_DisplayFirst( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp );
void ParseResponse_DisplayLast( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp );
void ParseResponse_Fire( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp );
void ParseResponse_Then( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp );
void ParseResponseGroup_Start( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams );
void ParseResponseGroup_PreDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams );
void ParseResponseGroup_NoDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams );
void ParseResponseGroup_DefaultDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams );
void ParseResponseGroup_Delay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams );
void ParseResponseGroup_SpeakOnce( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams );
void ParseResponseGroup_NoScene( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams );
void ParseResponseGroup_StopOnNonIdle( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams );
void ParseResponseGroup_Odds( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams );
void ParseResponseGroup_RespeakDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams );
void ParseResponseGroup_WeaponDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams );
void ParseResponseGroup_Soundlevel( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams );
public:
int ParseOneCriterion( const char *criterionName );
bool Compare( const char *setValue, Criteria *c, bool verbose = false );
bool CompareUsingMatcher( const char *setValue, Matcher& m, bool verbose = false );
void ComputeMatcher( Criteria *c, Matcher& matcher );
void ResolveToken( Matcher& matcher, char *token, size_t bufsize, char const *rawtoken );
float LookupEnumeration( const char *name, bool& found );
ResponseRulePartition::tIndex FindBestMatchingRule( const CriteriaSet& set, bool verbose, float &scoreOfBestMatchingRule );
float ScoreCriteriaAgainstRule( const CriteriaSet& set, ResponseRulePartition::tRuleDict &dict, int irule, bool verbose = false );
float RecursiveScoreSubcriteriaAgainstRule( const CriteriaSet& set, Criteria *parent, bool& exclude, bool verbose /*=false*/ );
float ScoreCriteriaAgainstRuleCriteria( const CriteriaSet& set, int icriterion, bool& exclude, bool verbose = false );
void FakeDepletes( ResponseGroup *g, IResponseFilter *pFilter );
void RevertFakedDepletes( ResponseGroup *g );
bool GetBestResponse( ResponseSearchResult& result, Rule *rule, bool verbose = false, IResponseFilter *pFilter = NULL );
bool ResolveResponse( ResponseSearchResult& result, int depth, const char *name, bool verbose = false, IResponseFilter *pFilter = NULL );
int SelectWeightedResponseFromResponseGroup( ResponseGroup *g, IResponseFilter *pFilter );
void DescribeResponseGroup( ResponseGroup *group, int selected, int depth );
void DebugPrint( int depth, const char *fmt, ... );
void LoadFromBuffer( const char *scriptfile, const char *buffer );
void GetCurrentScript( char *buf, size_t buflen );
int GetCurrentToken() const;
void SetCurrentScript( const char *script );
inline bool IsRootCommand( unsigned int hash ) const
{
int slot = m_RootCommandHashes.Find( hash );
return slot != m_RootCommandHashes.InvalidIndex();
}
inline bool IsRootCommand() const
{
return IsRootCommand( RR_HASH( token ) );
}
void PushScript( const char *scriptfile, unsigned char *buffer );
void PopScript(void);
void ResponseWarning( const char *fmt, ... );
CUtlDict< ResponseGroup, short > m_Responses;
CUtlDict< Criteria, short > m_Criteria;
// CUtlDict< Rule, short > m_Rules;
ResponseRulePartition m_RulePartitions;
CUtlDict< Enumeration, short > m_Enumerations;
CUtlVector<int> m_FakedDepletes;
char token[ 1204 ];
bool m_bUnget;
bool m_bCustomManagable;
struct ScriptEntry
{
unsigned char *buffer;
FileNameHandle_t name;
const char *currenttoken;
int tokencount;
};
CUtlVector< ScriptEntry > m_ScriptStack;
CStringPool m_IncludedFiles;
DispatchMap_t m_FileDispatch;
ParseRuleDispatchMap_t m_RuleDispatch;
ParseResponseDispatchMap_t m_ResponseDispatch;
ParseResponseGroupDispatchMap_t m_ResponseGroupDispatch;
CUtlRBTree< unsigned int > m_RootCommandHashes;
// for debugging purposes only: concepts to be emitted from rr_debugresponses 2
typedef CUtlLinkedList< CRR_Concept, unsigned short, false, unsigned int > ExcludeList_t;
static ExcludeList_t m_DebugExcludeList;
friend class CDefaultResponseSystemSaveRestoreBlockHandler;
friend class CResponseSystemSaveRestoreOps;
};
// Some globals inherited from AI_Speech.h:
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
}
#endif // RESPONSE_SYSTEM_H

View File

@ -0,0 +1,279 @@
//========= Copyright © 1996-2010, Valve Corporation, All rights reserved. ============//
//
// Purpose: Core types for the response rules -- criteria, responses, rules, and matchers.
//
// $NoKeywords: $
//=============================================================================//
#include "rrbase.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
using namespace ResponseRules;
// bizarre function handed down from the misty days of yore
// and the original response system. a lot of stuff uses it
// and I can't be arsed to replace everything with the c stdlib
// stuff
namespace ResponseRules
{
extern const char *ResponseCopyString( const char *in );
};
//-------------------- MATCHER ----------------------------------------------
Matcher::Matcher()
{
valid = false;
isnumeric = false;
notequal = false;
usemin = false;
minequals = false;
usemax = false;
maxequals = false;
#ifdef MAPBASE
isbit = false;
#endif
maxval = 0.0f;
minval = 0.0f;
token = UTL_INVAL_SYMBOL;
rawtoken = UTL_INVAL_SYMBOL;
}
void Matcher::Describe( void )
{
if ( !valid )
{
DevMsg( " invalid!\n" );
return;
}
char sz[ 128 ];
sz[ 0] = 0;
int minmaxcount = 0;
if ( usemin )
{
Q_snprintf( sz, sizeof( sz ), ">%s%.3f", minequals ? "=" : "", minval );
minmaxcount++;
}
if ( usemax )
{
char sz2[ 128 ];
Q_snprintf( sz2, sizeof( sz2 ), "<%s%.3f", maxequals ? "=" : "", maxval );
if ( minmaxcount > 0 )
{
Q_strncat( sz, " and ", sizeof( sz ), COPY_ALL_CHARACTERS );
}
Q_strncat( sz, sz2, sizeof( sz ), COPY_ALL_CHARACTERS );
minmaxcount++;
}
if ( minmaxcount >= 1 )
{
DevMsg( " matcher: %s\n", sz );
return;
}
#ifdef MAPBASE
if ( isbit )
{
DevMsg( " matcher: &%s%s\n", notequal ? "~" : "", GetToken() );
return;
}
#endif
if ( notequal )
{
DevMsg( " matcher: !=%s\n", GetToken() );
return;
}
DevMsg( " matcher: ==%s\n", GetToken() );
}
void Matcher::SetToken( char const *s )
{
token = g_RS.AddString( s );
}
void Matcher::SetRaw( char const *raw )
{
rawtoken = g_RS.AddString( raw );
}
char const *Matcher::GetToken()
{
if ( token.IsValid() )
{
return g_RS.String( token );
}
return "";
}
char const *Matcher::GetRaw()
{
if ( rawtoken.IsValid() )
{
return g_RS.String( rawtoken );
}
return "";
}
//-------------------- CRITERIA ----------------------------------------------
Criteria::Criteria()
{
value = NULL;
weight.SetFloat( 1.0f );
required = false;
}
Criteria::Criteria(const Criteria& src )
{
operator=( src );
}
Criteria::~Criteria()
{
// do nothing because we don't own name and value anymore
}
Criteria& Criteria::operator =(const Criteria& src )
{
if ( this == &src )
return *this;
nameSym = src.nameSym;
value = ResponseCopyString( src.value );
weight = src.weight;
required = src.required;
matcher = src.matcher;
int c = src.subcriteria.Count();
subcriteria.EnsureCapacity( c );
for ( int i = 0; i < c; i++ )
{
subcriteria.AddToTail( src.subcriteria[ i ] );
}
return *this;
}
//-------------------- RESPONSE ----------------------------------------------
ParserResponse::ParserResponse() : m_followup()
{
type = RESPONSE_NONE;
value = NULL;
weight.SetFloat( 1.0f );
depletioncount = 0;
first = false;
last = false;
}
ParserResponse& ParserResponse::operator =( const ParserResponse& src )
{
if ( this == &src )
return *this;
weight = src.weight;
type = src.type;
value = ResponseCopyString( src.value );
depletioncount = src.depletioncount;
first = src.first;
last = src.last;
params = src.params;
m_followup.followup_concept = ResponseCopyString(src.m_followup.followup_concept);
m_followup.followup_contexts = ResponseCopyString(src.m_followup.followup_contexts);
m_followup.followup_target = ResponseCopyString(src.m_followup.followup_target);
m_followup.followup_entityioinput = ResponseCopyString(src.m_followup.followup_entityioinput);
m_followup.followup_entityiotarget = ResponseCopyString(src.m_followup.followup_entityiotarget);
m_followup.followup_delay = src.m_followup.followup_delay;
m_followup.followup_entityiodelay = src.m_followup.followup_entityiodelay;
return *this;
}
ParserResponse::ParserResponse( const ParserResponse& src )
{
operator=( src );
}
ParserResponse::~ParserResponse()
{
// nothing to do, since we don't own
// the strings anymore
}
// ------------ RULE ---------------
Rule::Rule() : m_nForceWeight(0)
{
m_bMatchOnce = false;
m_bEnabled = true;
m_szContext = NULL;
#ifdef MAPBASE
m_iContextFlags = 0;
#else
m_bApplyContextToWorld = false;
#endif
}
Rule& Rule::operator =( const Rule& src )
{
if ( this == &src )
return *this;
int i;
int c;
c = src.m_Criteria.Count();
m_Criteria.EnsureCapacity( c );
for ( i = 0; i < c; i++ )
{
m_Criteria.AddToTail( src.m_Criteria[ i ] );
}
c = src.m_Responses.Count();
m_Responses.EnsureCapacity( c );
for ( i = 0; i < c; i++ )
{
m_Responses.AddToTail( src.m_Responses[ i ] );
}
SetContext( src.m_szContext );
m_bMatchOnce = src.m_bMatchOnce;
m_bEnabled = src.m_bEnabled;
#ifdef MAPBASE
m_iContextFlags = src.m_iContextFlags;
#else
m_bApplyContextToWorld = src.m_bApplyContextToWorld;
#endif
m_nForceWeight = src.m_nForceWeight;
return *this;
}
Rule::Rule( const Rule& src )
{
operator=(src);
}
Rule::~Rule()
{
}
void Rule::SetContext( const char *context )
{
// we don't own the data we point to, so just update pointer
m_szContext = ResponseCopyString( context );
}

View File

@ -0,0 +1,120 @@
//========= Copyright © 1996-2010, Valve Corporation, All rights reserved. ============//
//
// Purpose: Core types for the response rules -- criteria, responses, rules, and matchers.
//
// $NoKeywords: $
//=============================================================================//
#include "rrbase.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
using namespace ResponseRules;
ResponseRulePartition::ResponseRulePartition()
{
Assert(true);
COMPILE_TIME_ASSERT( kIDX_ELEM_MASK < (1 << 16) );
COMPILE_TIME_ASSERT( (kIDX_ELEM_MASK & (kIDX_ELEM_MASK + 1)) == 0 ); /// assert is power of two minus one
}
ResponseRulePartition::~ResponseRulePartition()
{
RemoveAll();
}
ResponseRulePartition::tIndex ResponseRulePartition::IndexFromDictElem( tRuleDict* pDict, int elem )
{
Assert( pDict );
// If this fails, you've tried to build an index for a rule that's not stored
// in this partition
Assert( pDict >= m_RuleParts && pDict < m_RuleParts + N_RESPONSE_PARTITIONS );
AssertMsg1( elem <= kIDX_ELEM_MASK, "A rule dictionary has %d elements; this exceeds the 255 that can be packed into an index.\n", elem );
int bucket = pDict - m_RuleParts;
return ( bucket << 16 ) | ( elem & kIDX_ELEM_MASK ); // this is a native op on PPC
}
char const *ResponseRulePartition::GetElementName( const tIndex &i ) const
{
Assert( IsValid(i) );
return m_RuleParts[ BucketFromIdx(i) ].GetElementName( PartFromIdx(i) );
}
int ResponseRulePartition::Count( void )
{
int count = 0 ;
for ( int bukkit = 0 ; bukkit < N_RESPONSE_PARTITIONS ; ++bukkit )
{
count += m_RuleParts[bukkit].Count();
}
return count;
}
void ResponseRulePartition::RemoveAll( void )
{
for ( int bukkit = 0 ; bukkit < N_RESPONSE_PARTITIONS ; ++bukkit )
{
for ( int i = m_RuleParts[bukkit].FirstInorder(); i != m_RuleParts[bukkit].InvalidIndex(); i = m_RuleParts[bukkit].NextInorder( i ) )
{
delete m_RuleParts[bukkit][ i ];
}
m_RuleParts[bukkit].RemoveAll();
}
}
// don't bucket "subject" criteria that prefix with operators, since stripping all that out again would
// be a big pain, and the most important rules that need subjects are tlk_remarks anyway.
static inline bool CanBucketBySubject( const char * RESTRICT pszSubject )
{
return pszSubject &&
( ( pszSubject[0] >= 'A' && pszSubject[0] <= 'Z' ) ||
( pszSubject[0] >= 'a' && pszSubject[0] <= 'z' ) );
}
ResponseRulePartition::tRuleDict &ResponseRulePartition::GetDictForRule( CResponseSystem *pSystem, Rule *pRule )
{
const static CUtlSymbol kWHO = CriteriaSet::ComputeCriteriaSymbol("Who");
const static CUtlSymbol kCONCEPT = CriteriaSet::ComputeCriteriaSymbol("Concept");
const static CUtlSymbol kSUBJECT = CriteriaSet::ComputeCriteriaSymbol("Subject");
const char *pszSpeaker = pRule->GetValueForRuleCriterionByName( pSystem, kWHO );
const char *pszConcept = pRule->GetValueForRuleCriterionByName( pSystem, kCONCEPT );
const Criteria *pSubjCrit = pRule->GetPointerForRuleCriterionByName( pSystem, kSUBJECT );
return m_RuleParts[
GetBucketForSpeakerAndConcept( pszSpeaker, pszConcept,
( pSubjCrit && pSubjCrit->required && CanBucketBySubject(pSubjCrit->value) ) ?
pSubjCrit->value :
NULL )
];
}
void ResponseRulePartition::GetDictsForCriteria( CUtlVectorFixed< ResponseRulePartition::tRuleDict *, 2 > *pResult, const CriteriaSet &criteria )
{
pResult->RemoveAll();
pResult->EnsureCapacity( 2 );
// get the values for Who and Concept, which are what we bucket on
int speakerIdx = criteria.FindCriterionIndex( "Who" );
const char *pszSpeaker = speakerIdx != -1 ? criteria.GetValue( speakerIdx ) : NULL ;
int conceptIdx = criteria.FindCriterionIndex( "Concept" );
const char *pszConcept = conceptIdx != -1 ? criteria.GetValue( conceptIdx ) : NULL ;
int subjectIdx = criteria.FindCriterionIndex( "Subject" );
const char *pszSubject = subjectIdx != -1 ? criteria.GetValue( subjectIdx ) : NULL ;
pResult->AddToTail( &m_RuleParts[ GetBucketForSpeakerAndConcept(pszSpeaker, pszConcept, pszSubject) ] );
// also try the rules not specifying subject
pResult->AddToTail( &m_RuleParts[ GetBucketForSpeakerAndConcept(pszSpeaker, pszConcept, NULL) ] );
}

View File

@ -0,0 +1,553 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Core types for the response rules -- criteria, responses, rules, and matchers.
//
// $NoKeywords: $
//=============================================================================//
#ifndef RESPONSE_TYPES_INTERNAL_H
#define RESPONSE_TYPES_INTERNAL_H
#ifdef _WIN32
#pragma once
#endif
#include "responserules/response_types.h"
#include "utldict.h"
namespace ResponseRules
{
inline unsigned FASTCALL HashStringConventional( const char *pszKey )
{
unsigned hash = 0xAAAAAAAA; // Alternating 1's and 0's to maximize the effect of the later multiply and add
for( ; *pszKey ; pszKey++ )
{
hash = ( ( hash << 5 ) + hash ) + (uint8)(*pszKey);
}
return hash;
}
// Note: HashString causes collisions!!!
#define RR_HASH HashStringConventional
#pragma pack(push,1)
class Matcher
{
public:
Matcher();
void Describe( void );
float maxval;
float minval;
bool valid : 1; //1
bool isnumeric : 1; //2
bool notequal : 1; //3
bool usemin : 1; //4
bool minequals : 1; //5
bool usemax : 1; //6
bool maxequals : 1; //7
#ifdef MAPBASE
bool isbit : 1; //8
#endif
void SetToken( char const *s );
char const *GetToken();
void SetRaw( char const *raw );
char const *GetRaw();
private:
CUtlSymbol token;
CUtlSymbol rawtoken;
};
#pragma pack(pop)
struct Criteria
{
Criteria();
Criteria& operator =(const Criteria& src );
Criteria(const Criteria& src );
~Criteria();
// Does this criterion recursively contain more criteria?
inline bool IsSubCriteriaType() const
{
return ( subcriteria.Count() > 0 ) ? true : false;
}
// const char *name;
CUtlSymbol nameSym;
const char *value;
float16 weight;
bool required;
Matcher matcher;
// Indices into sub criteria
CUtlVectorConservative< unsigned short > subcriteria;
};
#pragma pack(push,1)
/// This is a response block as read from the file,
/// different from CRR_Response which is what is handed
/// back to queries.
struct ParserResponse
{
DECLARE_SIMPLE_DATADESC_INSIDE_NAMESPACE();
ParserResponse();
ParserResponse( const ParserResponse& src );
ParserResponse& operator =( const ParserResponse& src );
~ParserResponse();
ResponseType_t GetType() { return (ResponseType_t)type; }
ResponseParams params;
const char *value; // fixed up value spot // 4
float16 weight; // 6
byte depletioncount; // 7
byte type : 6; // 8
byte first : 1; //
byte last : 1; //
ALIGN32 AI_ResponseFollowup m_followup; // info on whether I should force the other guy to say something
};
#pragma pack(pop)
#pragma pack(push,1)
struct ResponseGroup
{
DECLARE_SIMPLE_DATADESC_INSIDE_NAMESPACE();
ResponseGroup()
{
// By default visit all nodes before repeating
m_bSequential = false;
m_bNoRepeat = false;
m_bEnabled = true;
m_nCurrentIndex = 0;
m_bDepleteBeforeRepeat = true;
m_nDepletionCount = 1;
m_bHasFirst = false;
m_bHasLast = false;
}
ResponseGroup( const ResponseGroup& src )
{
int c = src.group.Count();
for ( int i = 0; i < c; i++ )
{
group.AddToTail( src.group[ i ] );
}
m_bDepleteBeforeRepeat = src.m_bDepleteBeforeRepeat;
m_nDepletionCount = src.m_nDepletionCount;
m_bHasFirst = src.m_bHasFirst;
m_bHasLast = src.m_bHasLast;
m_bSequential = src.m_bSequential;
m_bNoRepeat = src.m_bNoRepeat;
m_bEnabled = src.m_bEnabled;
m_nCurrentIndex = src.m_nCurrentIndex;
}
ResponseGroup& operator=( const ResponseGroup& src )
{
if ( this == &src )
return *this;
int c = src.group.Count();
for ( int i = 0; i < c; i++ )
{
group.AddToTail( src.group[ i ] );
}
m_bDepleteBeforeRepeat = src.m_bDepleteBeforeRepeat;
m_nDepletionCount = src.m_nDepletionCount;
m_bHasFirst = src.m_bHasFirst;
m_bHasLast = src.m_bHasLast;
m_bSequential = src.m_bSequential;
m_bNoRepeat = src.m_bNoRepeat;
m_bEnabled = src.m_bEnabled;
m_nCurrentIndex = src.m_nCurrentIndex;
return *this;
}
bool HasUndepletedChoices() const
{
if ( !m_bDepleteBeforeRepeat )
return true;
int c = group.Count();
for ( int i = 0; i < c; i++ )
{
if ( group[ i ].depletioncount != m_nDepletionCount )
return true;
}
return false;
}
void MarkResponseUsed( int idx )
{
if ( !m_bDepleteBeforeRepeat )
return;
if ( idx < 0 || idx >= group.Count() )
{
Assert( 0 );
return;
}
group[ idx ].depletioncount = m_nDepletionCount;
}
void ResetDepletionCount()
{
if ( !m_bDepleteBeforeRepeat )
return;
++m_nDepletionCount;
}
void Reset()
{
ResetDepletionCount();
SetEnabled( true );
SetCurrentIndex( 0 );
m_nDepletionCount = 1;
for ( int i = 0; i < group.Count(); ++i )
{
group[ i ].depletioncount = 0;
}
}
bool HasUndepletedFirst( int& index )
{
index = -1;
if ( !m_bDepleteBeforeRepeat )
return false;
int c = group.Count();
for ( int i = 0; i < c; i++ )
{
ParserResponse *r = &group[ i ];
if ( ( r->depletioncount != m_nDepletionCount ) && r->first )
{
index = i;
return true;
}
}
return false;
}
bool HasUndepletedLast( int& index )
{
index = -1;
if ( !m_bDepleteBeforeRepeat )
return false;
int c = group.Count();
for ( int i = 0; i < c; i++ )
{
ParserResponse *r = &group[ i ];
if ( ( r->depletioncount != m_nDepletionCount ) && r->last )
{
index = i;
return true;
}
}
return false;
}
bool ShouldCheckRepeats() const { return m_bDepleteBeforeRepeat; }
int GetDepletionCount() const { return m_nDepletionCount; }
bool IsSequential() const { return m_bSequential; }
void SetSequential( bool seq ) { m_bSequential = seq; }
bool IsNoRepeat() const { return m_bNoRepeat; }
void SetNoRepeat( bool norepeat ) { m_bNoRepeat = norepeat; }
bool IsEnabled() const { return m_bEnabled; }
void SetEnabled( bool enabled ) { m_bEnabled = enabled; }
int GetCurrentIndex() const { return m_nCurrentIndex; }
void SetCurrentIndex( byte idx ) { m_nCurrentIndex = idx; }
CUtlVector< ParserResponse > group;
bool m_bEnabled;
byte m_nCurrentIndex;
// Invalidation counter
byte m_nDepletionCount;
// Use all slots before repeating any
bool m_bDepleteBeforeRepeat : 1;
bool m_bHasFirst : 1;
bool m_bHasLast : 1;
bool m_bSequential : 1;
bool m_bNoRepeat : 1;
};
#pragma pack(pop)
#pragma pack(push,1)
struct Rule
{
Rule();
Rule( const Rule& src );
~Rule();
Rule& operator =( const Rule& src );
void SetContext( const char *context );
const char *GetContext( void ) const { return m_szContext; }
inline bool IsEnabled() const { return m_bEnabled; }
inline void Disable() { m_bEnabled = false; }
inline bool IsMatchOnce() const { return m_bMatchOnce; }
#ifdef MAPBASE
inline int GetContextFlags() const { return m_iContextFlags; }
inline bool IsApplyContextToWorld() const { return (m_iContextFlags & APPLYCONTEXT_WORLD) != 0; }
#else
inline bool IsApplyContextToWorld() const { return m_bApplyContextToWorld; }
#endif
const char *GetValueForRuleCriterionByName( CResponseSystem *pSystem, const CUtlSymbol &pCritNameSym );
const Criteria *GetPointerForRuleCriterionByName( CResponseSystem *pSystem, const CUtlSymbol &pCritNameSym );
// Indices into underlying criteria and response dictionaries
CUtlVectorConservative< unsigned short > m_Criteria;
CUtlVectorConservative< unsigned short> m_Responses;
const char *m_szContext;
uint8 m_nForceWeight;
#ifdef MAPBASE
int m_iContextFlags;
#else
bool m_bApplyContextToWorld : 1;
#endif
bool m_bMatchOnce : 1;
bool m_bEnabled : 1;
private:
// what is this, lisp?
const char *RecursiveGetValueForRuleCriterionByName( CResponseSystem *pSystem, const Criteria *pCrit, const CUtlSymbol &pCritNameSym );
const Criteria *RecursiveGetPointerForRuleCriterionByName( CResponseSystem *pSystem, const Criteria *pCrit, const CUtlSymbol &pCritNameSym );
};
#pragma pack(pop)
template <typename T, typename I = unsigned short>
class CResponseDict : public CUtlMap<unsigned int, T, I>
{
public:
CResponseDict() : CUtlMap<unsigned int, T, I>( DefLessFunc( unsigned int ) ), m_ReverseMap( DefLessFunc( unsigned int ) )
{
}
I Insert( const char *pName, const T &element )
{
char const *pString = ResponseCopyString( pName );
unsigned int hash = RR_HASH( pString );
m_ReverseMap.Insert( hash, pString );
return CUtlMap<unsigned int, T, I>::Insert( hash, element );
}
I Insert( const char *pName )
{
char const *pString = ResponseCopyString( pName );
unsigned int hash = RR_HASH( pString );
m_ReverseMap.Insert( hash, pString );
return CUtlMap<unsigned int, T, I>::Insert( hash );
}
I Find( char const *pName ) const
{
unsigned int hash = RR_HASH( pName );
return CUtlMap<unsigned int, T, I>::Find( hash );
}
const char *GetElementName( I i )
{
int k = Key( i );
int slot = m_ReverseMap.Find( k );
if ( slot == m_ReverseMap.InvalidIndex() )
return "";
return m_ReverseMap[ slot ];
}
const char *GetElementName( I i ) const
{
int k = Key( i );
int slot = m_ReverseMap.Find( k );
if ( slot == m_ReverseMap.InvalidIndex() )
return "";
return m_ReverseMap[ slot ];
}
private:
CUtlMap< unsigned int, const char * > m_ReverseMap;
};
// define this to 1 to enable printing some occupancy
// information on the response system via concommmand
// rr_dumphashinfo
#define RR_DUMPHASHINFO_ENABLED 0
// The Rules are partitioned based on a variety of factors (presently,
// speaker and concept) for faster lookup, basically a seperate-chained hash.
struct ResponseRulePartition
{
ResponseRulePartition( void );
~ResponseRulePartition();
typedef CResponseDict< Rule * > tRuleDict;
typedef uint32 tIndex; // an integer that can be used to find any rule in the dict
/// get the appropriate m_rules dict for the provided rule
tRuleDict &GetDictForRule( CResponseSystem *pSystem, Rule *pRule );
/// get all bucket full of rules that might possibly match the given criteria.
/// (right now they are bucketed such that all rules that can possibly match a
/// criteria are in one of two dictionaries)
void GetDictsForCriteria( CUtlVectorFixed< ResponseRulePartition::tRuleDict *, 2 > *pResult, const CriteriaSet &criteria );
// dump everything.
void RemoveAll();
inline Rule &operator[]( tIndex idx );
int Count( void ); // number of elements inside, but you can't iterate from 0 to this
char const *GetElementName( const tIndex &i ) const;
/// given a dictionary and an element number inside that dict,
/// return a tIndex
tIndex IndexFromDictElem( tRuleDict* pDict, int elem );
// for iteration:
inline tIndex First( void );
inline tIndex Next( const tIndex &idx );
inline bool IsValid( const tIndex &idx ) const;
inline static tIndex InvalidIdx( void )
{
return ((tIndex) -1);
}
// used only for debug prints, do not rely on them otherwise
inline unsigned int BucketFromIdx( const tIndex &idx ) const ;
inline unsigned int PartFromIdx( const tIndex &idx ) const ;
enum {
N_RESPONSE_PARTITIONS = 256,
kIDX_ELEM_MASK = 0xFFF, ///< this is used to mask the element number part of a ResponseRulePartition::tIndex
};
#if RR_DUMPHASHINFO_ENABLED
void PrintBucketInfo( CResponseSystem *pSys );
#endif
private:
tRuleDict m_RuleParts[N_RESPONSE_PARTITIONS];
unsigned int GetBucketForSpeakerAndConcept( const char *pszSpeaker, const char *pszConcept, const char *pszSubject );
};
// // // // // inline functions
inline ResponseRulePartition::tIndex ResponseRulePartition::First( void )
{
// find the first bucket that has anything
for ( int bucket = 0 ; bucket < N_RESPONSE_PARTITIONS; bucket++ )
{
if ( m_RuleParts[bucket].Count() > 0 )
return bucket << 16;
}
return InvalidIdx();
}
inline ResponseRulePartition::tIndex ResponseRulePartition::Next( const tIndex &idx )
{
int bucket = BucketFromIdx( idx );
unsigned int elem = PartFromIdx( idx );
Assert( IsValid(idx) );
AssertMsg( elem < kIDX_ELEM_MASK, "Too many response rules! Overflow! Doom!" );
if ( elem + 1 < m_RuleParts[bucket].Count() )
{
return idx+1;
}
else
{
// walk through the other buckets, skipping empty ones, until we find one with responses and give up.
while ( ++bucket < N_RESPONSE_PARTITIONS )
{
if ( m_RuleParts[bucket].Count() > 0 )
{
// 0th element in nth bucket
return bucket << 16;
}
}
// out of buckets
return InvalidIdx();
}
}
inline Rule &ResponseRulePartition::operator[]( tIndex idx )
{
Assert( IsValid(idx) );
return *m_RuleParts[ BucketFromIdx(idx) ][ PartFromIdx(idx) ] ;
}
inline unsigned int ResponseRulePartition::BucketFromIdx( const tIndex &idx ) const
{
return idx >> 16;
}
inline unsigned int ResponseRulePartition::PartFromIdx( const tIndex &idx ) const
{
return idx & kIDX_ELEM_MASK;
}
inline bool ResponseRulePartition::IsValid( const tIndex & idx ) const
{
// make sure that the idx type for the dicts is still short
COMPILE_TIME_ASSERT( sizeof(m_RuleParts[0].FirstInorder()) == 2 );
if ( idx == -1 )
return false;
int bucket = idx >> 16;
unsigned int elem = idx & kIDX_ELEM_MASK;
return ( bucket < N_RESPONSE_PARTITIONS &&
elem < m_RuleParts[bucket].Count() );
}
//-----------------------------------------------------------------------------
// PARSER TYPES -- these are internal to the response system, and represent
// the objects as loaded from disk.
//-----------------------------------------------------------------------------
}
#include "response_system.h"
#endif

View File

@ -0,0 +1,14 @@
//========= Copyright © 1996-2010, Valve Corporation, All rights reserved. ============//
//
// Purpose: Convars used by the response rule system.
//
// $NoKeywords: $
//=============================================================================//
#include "rrbase.h"
#include <convar.h>
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

View File

@ -0,0 +1,371 @@
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $NoKeywords: $
//
//===========================================================================//
#include "rrbase.h"
#include <tier1/interval.h>
/*
#include "AI_Criteria.h"
#include "ai_speech.h"
#include <KeyValues.h>
#include "engine/IEngineSound.h"
*/
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
using namespace ResponseRules;
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CRR_Response::CRR_Response() : m_fMatchScore(0)
{
m_Type = ResponseRules::RESPONSE_NONE;
m_szResponseName[0] = 0;
m_szMatchingRule[0]=0;
m_szContext = NULL;
#ifdef MAPBASE
m_iContextFlags = 0;
#else
m_bApplyContextToWorld = false;
#endif
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CRR_Response::CRR_Response( const CRR_Response &from ) : m_fMatchScore(0)
{
// Assert( (void*)(&m_Type) == (void*)this );
Invalidate();
memcpy( this, &from, sizeof(*this) );
m_szContext = NULL;
SetContext( from.m_szContext );
#ifdef MAPBASE
m_iContextFlags = from.m_iContextFlags;
#else
m_bApplyContextToWorld = from.m_bApplyContextToWorld;
#endif
}
//-----------------------------------------------------------------------------
CRR_Response &CRR_Response::operator=( const CRR_Response &from )
{
// Assert( (void*)(&m_Type) == (void*)this );
Invalidate();
memcpy( this, &from, sizeof(*this) );
m_szContext = NULL;
SetContext( from.m_szContext );
#ifdef MAPBASE
m_iContextFlags = from.m_iContextFlags;
#else
m_bApplyContextToWorld = from.m_bApplyContextToWorld;
#endif
return *this;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CRR_Response::~CRR_Response()
{
if (m_szContext)
delete[] m_szContext;
}
void CRR_Response::Invalidate()
{
if (m_szContext)
{
delete[] m_szContext;
m_szContext = NULL;
}
m_Type = ResponseRules::RESPONSE_NONE;
m_szResponseName[0] = 0;
// not really necessary:
/*
m_szMatchingRule[0]=0;
m_szContext = NULL;
m_bApplyContextToWorld = false;
*/
}
// please do not new or delete CRR_Responses.
void CRR_Response::operator delete(void* p)
{
AssertMsg(false, "DO NOT new or delete CRR_Response s.");
free(p);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *response -
// *criteria -
//-----------------------------------------------------------------------------
void CRR_Response::Init( ResponseType_t type, const char *responseName, const ResponseParams& responseparams, const char *ruleName, const char *applyContext, bool bApplyContextToWorld )
{
m_Type = type;
Q_strncpy( m_szResponseName, responseName, sizeof( m_szResponseName ) );
// Copy underlying criteria
Q_strncpy( m_szMatchingRule, ruleName ? ruleName : "NULL", sizeof( m_szMatchingRule ) );
m_Params = responseparams;
SetContext( applyContext );
#ifdef MAPBASE
bApplyContextToWorld ? m_iContextFlags = APPLYCONTEXT_WORLD : m_iContextFlags = 0;
#else
m_bApplyContextToWorld = bApplyContextToWorld;
#endif
}
#ifdef MAPBASE
//-----------------------------------------------------------------------------
// Purpose:
// Input : *response -
// *criteria -
//-----------------------------------------------------------------------------
void CRR_Response::Init( ResponseType_t type, const char *responseName, const ResponseParams& responseparams, const char *ruleName, const char *applyContext, int iContextFlags )
{
m_Type = type;
Q_strncpy( m_szResponseName, responseName, sizeof( m_szResponseName ) );
// Copy underlying criteria
Q_strncpy( m_szMatchingRule, ruleName ? ruleName : "NULL", sizeof( m_szMatchingRule ) );
m_Params = responseparams;
SetContext( applyContext );
m_iContextFlags = iContextFlags;
}
#endif
//-----------------------------------------------------------------------------
// Purpose: Debug-print the response. You can optionally pass in the criteria
// used to come up with this response (usually present in the calling function)
// if you want to print that as well. DO NOT store the entire criteria set in
// CRR_Response just to make this debug print cleaner.
//-----------------------------------------------------------------------------
void CRR_Response::Describe( const CriteriaSet *pDebugCriteria )
{
if ( pDebugCriteria )
{
DevMsg( "Search criteria:\n" );
pDebugCriteria->Describe();
}
if ( m_szMatchingRule[ 0 ] )
{
DevMsg( "Matched rule '%s', ", m_szMatchingRule );
}
if ( m_szContext )
{
#ifdef MAPBASE
DevMsg( "Contexts to set '%s' on ", m_szContext );
if (m_iContextFlags & APPLYCONTEXT_WORLD)
DevMsg( "world, " );
else if (m_iContextFlags & APPLYCONTEXT_SQUAD)
DevMsg( "squad, " );
else if (m_iContextFlags & APPLYCONTEXT_ENEMY)
DevMsg( "enemy, " );
else
DevMsg( "speaker, " );
#else
DevMsg( "Contexts to set '%s' on %s, ", m_szContext, m_bApplyContextToWorld ? "world" : "speaker" );
#endif
}
DevMsg( "response %s = '%s'\n", DescribeResponse( (ResponseType_t)m_Type ), m_szResponseName );
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : char const
//-----------------------------------------------------------------------------
void CRR_Response::GetName( char *buf, size_t buflen ) const
{
Q_strncpy( buf, m_szResponseName, buflen );
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : char const
//-----------------------------------------------------------------------------
void CRR_Response::GetResponse( char *buf, size_t buflen ) const
{
GetName( buf, buflen );
}
const char* ResponseRules::CRR_Response::GetNamePtr() const
{
return m_szResponseName;
}
const char* ResponseRules::CRR_Response::GetResponsePtr() const
{
return m_szResponseName;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : type -
// Output : char const
//-----------------------------------------------------------------------------
const char *CRR_Response::DescribeResponse( ResponseType_t type )
{
if ( (int)type < 0 || (int)type >= ResponseRules::NUM_RESPONSES )
{
Assert( 0 );
return "???CRR_Response bogus index";
}
switch( type )
{
default:
{
Assert( 0 );
}
// Fall through
case ResponseRules::RESPONSE_NONE:
return "RESPONSE_NONE";
case ResponseRules::RESPONSE_SPEAK:
return "RESPONSE_SPEAK";
case ResponseRules::RESPONSE_SENTENCE:
return "RESPONSE_SENTENCE";
case ResponseRules::RESPONSE_SCENE:
return "RESPONSE_SCENE";
case ResponseRules::RESPONSE_RESPONSE:
return "RESPONSE_RESPONSE";
case ResponseRules::RESPONSE_PRINT:
return "RESPONSE_PRINT";
case ResponseRules::RESPONSE_ENTITYIO:
return "RESPONSE_ENTITYIO";
#ifdef MAPBASE
case ResponseRules::RESPONSE_VSCRIPT:
return "RESPONSE_VSCRIPT";
#endif
}
return "RESPONSE_NONE";
}
/*
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CRR_Response::Release()
{
delete this;
}
*/
//-----------------------------------------------------------------------------
// Purpose:
// Output : soundlevel_t
//-----------------------------------------------------------------------------
soundlevel_t CRR_Response::GetSoundLevel() const
{
if ( m_Params.flags & ResponseParams::RG_SOUNDLEVEL )
{
return (soundlevel_t)m_Params.soundlevel;
}
return SNDLVL_TALKING;
}
float CRR_Response::GetRespeakDelay( void ) const
{
if ( m_Params.flags & ResponseParams::RG_RESPEAKDELAY )
{
interval_t temp;
m_Params.respeakdelay.ToInterval( temp );
return RandomInterval( temp );
}
return 0.0f;
}
float CRR_Response::GetWeaponDelay( void ) const
{
if ( m_Params.flags & ResponseParams::RG_WEAPONDELAY )
{
interval_t temp;
m_Params.weapondelay.ToInterval( temp );
return RandomInterval( temp );
}
return 0.0f;
}
bool CRR_Response::GetSpeakOnce( void ) const
{
if ( m_Params.flags & ResponseParams::RG_SPEAKONCE )
{
return true;
}
return false;
}
bool CRR_Response::ShouldntUseScene( void ) const
{
return ( m_Params.flags & ResponseParams::RG_DONT_USE_SCENE ) != 0;
}
bool CRR_Response::ShouldBreakOnNonIdle( void ) const
{
return ( m_Params.flags & ResponseParams::RG_STOP_ON_NONIDLE ) != 0;
}
int CRR_Response::GetOdds( void ) const
{
if ( m_Params.flags & ResponseParams::RG_ODDS )
{
return m_Params.odds;
}
return 100;
}
float CRR_Response::GetDelay() const
{
if ( m_Params.flags & ResponseParams::RG_DELAYAFTERSPEAK )
{
interval_t temp;
m_Params.delay.ToInterval( temp );
return RandomInterval( temp );
}
return 0.0f;
}
float CRR_Response::GetPreDelay() const
{
if ( m_Params.flags & ResponseParams::RG_DELAYBEFORESPEAK )
{
interval_t temp;
m_Params.predelay.ToInterval( temp );
return RandomInterval( temp );
}
return 0.0f;
}
//-----------------------------------------------------------------------------
// Purpose: Sets context string
// Output : void
//-----------------------------------------------------------------------------
void CRR_Response::SetContext( const char *context )
{
if (m_szContext)
{
delete[] m_szContext;
m_szContext = NULL;
}
if ( context )
{
int len = Q_strlen( context );
m_szContext = new char[ len + 1 ];
Q_memcpy( m_szContext, context, len );
m_szContext[ len ] = 0;
}
}

View File

@ -0,0 +1,73 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "rrbase.h"
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
#if RR_CONCEPTS_ARE_STRINGS
#pragma error("RR_CONCEPTS_ARE_STRINGS no longer supported")
#else
using namespace ResponseRules;
// Used to turn ad-hoc concept from strings into numbers.
CRR_ConceptSymbolTable *g_pRRConceptTable = NULL;
// Q&D hack to defer initialization of concept table until I can figure out where it
// really needs to come from.
static void InitializeRRConceptTable()
{
if (g_pRRConceptTable == NULL)
{
g_pRRConceptTable = new CRR_ConceptSymbolTable( 64, 64, true );
}
}
// construct from string
CRR_Concept::CRR_Concept(const char *fromString)
{
InitializeRRConceptTable();
m_iConcept = g_pRRConceptTable->AddString(fromString);
}
CRR_Concept &CRR_Concept::operator=(const char *fromString)
{
InitializeRRConceptTable();
m_iConcept = g_pRRConceptTable->AddString(fromString);
return *this;
}
bool CRR_Concept::operator==(const char *pszConcept)
{
int otherConcept = g_pRRConceptTable->Find(pszConcept);
return ( otherConcept != UTL_INVAL_SYMBOL && otherConcept == m_iConcept );
}
const char *CRR_Concept::GetStringConcept() const
{
InitializeRRConceptTable();
AssertMsg( m_iConcept.IsValid(), "AI Concept has invalid string symbol.\n" );
const char * retval = g_pRRConceptTable->String(m_iConcept);
AssertMsg( retval, "An RR_Concept couldn't find its string in the symbol table!\n" );
if (retval == NULL)
{
Warning( "An RR_Concept couldn't find its string in the symbol table!\n" );
retval = "";
}
return retval;
}
const char *CRR_Concept::GetStringForGenericId(tGenericId genericId)
{
InitializeRRConceptTable();
return g_pRRConceptTable->String(genericId);
}
#endif

View File

@ -0,0 +1,59 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef RRBASE_H
#define RRBASE_H
#ifdef _WIN32
#pragma once
#endif
#ifdef _WIN32
// Silence certain warnings
// #pragma warning(disable : 4244) // int or float down-conversion
// #pragma warning(disable : 4305) // int or float data truncation
// #pragma warning(disable : 4201) // nameless struct/union
// #pragma warning(disable : 4511) // copy constructor could not be generated
// #pragma warning(disable : 4675) // resolved overload was found by argument dependent lookup
#endif
#ifdef _DEBUG
#define DEBUG 1
#endif
// Misc C-runtime library headers
#include <math.h>
#include <ctype.h>
#include <stdio.h>
// tier 0
#include "tier0/dbg.h"
#include "tier0/platform.h"
#include "basetypes.h"
// tier 1
#include "tier1/strtools.h"
#include "utlvector.h"
#include "utlsymbol.h"
// tier 2
#include "string_t.h"
// Shared engine/DLL constants
#include "const.h"
#include "edict.h"
// app
#if defined(_X360)
#define DISABLE_DEBUG_HISTORY 1
#endif
#include "responserules/response_types.h"
#include "response_types_internal.h"
#include "responserules/response_host_interface.h"
#endif // CBASE_H

View File

@ -0,0 +1,13 @@
/// PLACEHOLDER FILE FOR RESPONSE RULES RUNTIME LIBRARY
#include "rrbase.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
namespace ResponseRules
{
/// Custom symbol table for the response rules.
CUtlSymbolTable g_RS;
};

View File

@ -0,0 +1,11 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Builds the precompiled header for the game DLL
//
// $NoKeywords: $
//=============================================================================//
#include "rrbase.h"
// NOTE: DO NOT ADD ANY CODE OR HEADERS TO THIS FILE!!!

View File

@ -39,6 +39,7 @@ $Project "tier1"
$File "generichash.cpp"
$File "ilocalize.cpp"
$File "interface.cpp"
$File "interval.cpp"
$File "KeyValues.cpp"
$File "kvpacker.cpp"
$File "lzmaDecoder.cpp"

View File

@ -23,6 +23,7 @@ $Group "game"
"tier1"
"vgui_controls"
"vscript"
"responserules"
}
$Group "shaders"
@ -41,6 +42,7 @@ $Group "everything"
"mathlib"
"motionmapper"
"phonemeextractor"
"responserules"
"qc_eyes"
"raytrace"
"server"
@ -66,3 +68,17 @@ $Group "dedicated"
"tier1"
}
$Group "maptools"
{
"vbsp"
"vrad_dll"
"vrad_launcher"
"vvis_dll"
"vvis_launcher"
"fgdlib"
"mathlib"
"raytrace"
"tier1"
"vscript"
}

View File

@ -46,6 +46,11 @@ $Project "server"
"game\server\server_episodic.vpc" [($WIN32||$X360||$POSIX) && $EPISODIC]
}
$Project "responserules"
{
"responserules\runtime\response_rules.vpc" [($WINDOWS||$X360||$POSIX) && $NEW_RESPONSE_SYSTEM]
}
$Project "mathlib"
{
"mathlib\mathlib.vpc" [$WINDOWS||$X360||$POSIX]

View File

@ -24,6 +24,9 @@ $Conditional MAPBASE_RPC "1"
// Toggles VScript implementation (note: interfaces still exist, just the provided implementation is not present)
$Conditional MAPBASE_VSCRIPT "1"
// Toggles the new Response System library based on the Alien Swarm SDK.
$Conditional NEW_RESPONSE_SYSTEM "1"
//-----------------------------------------------------------------------------
$Configuration "Debug"