Merge pull request #275 from Blixibon/mapbase/feature/response-followup-criteria-expansion

Better circumstantial criteria for followup responses
This commit is contained in:
Blixibon 2024-02-04 15:26:39 -06:00 committed by GitHub
commit 02f81094e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 153 additions and 6 deletions

View File

@ -88,6 +88,15 @@ static void DispatchComeback( CAI_ExpresserWithFollowup *pExpress, CBaseEntity *
// add in any provided contexts from the parameters onto the ones stored in the followup
criteria.Merge( followup.followup_contexts );
#ifdef MAPBASE
if (CAI_ExpresserSink *pSink = dynamic_cast<CAI_ExpresserSink *>(pRespondent))
{
criteria.AppendCriteria( "dist_from_issuer", UTIL_VarArgs( "%f", (pRespondent->GetAbsOrigin() - pSpeaker->GetAbsOrigin()).Length() ) );
g_ResponseQueueManager.GetQueue()->AppendFollowupCriteria( followup.followup_concept, criteria, pSink->GetSinkExpresser(), pSink, pRespondent, pSpeaker, kDRT_SPECIFIC );
pSink->Speak( followup.followup_concept, &criteria );
}
#else
// 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))
@ -99,6 +108,7 @@ static void DispatchComeback( CAI_ExpresserWithFollowup *pExpress, CBaseEntity *
{
pActor->Speak( followup.followup_concept, &criteria );
}
#endif
}
#if 0

View File

@ -1800,6 +1800,54 @@ bool CAI_PlayerAlly::IsAllowedToSpeak( AIConcept_t concept, bool bRespondingToPl
return true;
}
#ifdef MAPBASE
//-----------------------------------------------------------------------------
// Purpose: Specifically for player allies handling followup responses.
// Better-accounts for unknown concepts so that users are free in what they use.
//-----------------------------------------------------------------------------
bool CAI_PlayerAlly::IsAllowedToSpeakFollowup( AIConcept_t concept, CBaseEntity *pIssuer, bool bSpecific )
{
CAI_AllySpeechManager * pSpeechManager = GetAllySpeechManager();
ConceptInfo_t * pInfo = pSpeechManager->GetConceptInfo( concept );
ConceptCategory_t category = SPEECH_PRIORITY; // Must be SPEECH_PRIORITY to get around semaphore
if ( !IsOkToSpeak( category, true ) )
return false;
// If this followup is specifically targeted towards us, speak if we're not already speaking
// If it's meant to be spoken by anyone, respect speech delay and semaphore
if ( bSpecific )
{
if ( !GetExpresser()->CanSpeakAfterMyself() )
return false;
}
else
{
if ( !GetExpresser()->CanSpeak() )
return false;
CAI_TimedSemaphore *pSemaphore = GetExpresser()->GetMySpeechSemaphore( this );
if ( pSemaphore && !pSemaphore->IsAvailable( this ) )
{
// Only if the semaphore holder isn't the one dispatching the followup
if ( pSemaphore->GetOwner() != pIssuer )
return false;
}
}
if ( !pSpeechManager->ConceptDelayExpired( concept ) )
return false;
if ( ( pInfo && pInfo->flags & AICF_SPEAK_ONCE ) && GetExpresser()->SpokeConcept( concept ) )
return false;
if ( !GetExpresser()->CanSpeakConcept( concept ) )
return false;
return true;
}
#endif
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CAI_PlayerAlly::SpeakIfAllowed( AIConcept_t concept, const char *modifiers, bool bRespondingToPlayer, char *pszOutResponseChosen, size_t bufsize )

View File

@ -403,6 +403,9 @@ public:
bool ShouldSpeakRandom( AIConcept_t concept, int iChance );
bool IsAllowedToSpeak( AIConcept_t concept, bool bRespondingToPlayer = false );
#ifdef MAPBASE
bool IsAllowedToSpeakFollowup( AIConcept_t concept, CBaseEntity *pIssuer, bool bSpecific );
#endif
virtual bool SpeakIfAllowed( AIConcept_t concept, const char *modifiers = NULL, bool bRespondingToPlayer = false, char *pszOutResponseChosen = NULL, size_t bufsize = 0 );
#ifdef MAPBASE
virtual bool SpeakIfAllowed( AIConcept_t concept, AI_CriteriaSet& modifiers, bool bRespondingToPlayer = false, char *pszOutResponseChosen = NULL, size_t bufsize = 0 );

View File

@ -126,6 +126,13 @@ public:
virtual void OnSpokeConcept( AIConcept_t concept, AI_Response *response ) {};
virtual void OnStartSpeaking() {}
virtual bool UseSemaphore() { return true; }
#ifdef MAPBASE
// Works around issues with CAI_ExpresserHost<> class hierarchy
virtual CAI_Expresser *GetSinkExpresser() { return NULL; }
virtual bool IsAllowedToSpeakFollowup( AIConcept_t concept, CBaseEntity *pIssuer, bool bSpecific ) { return true; }
virtual bool Speak( AIConcept_t concept, AI_CriteriaSet *pCriteria, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL ) { return false; }
#endif
};
struct ConceptHistory_t
@ -244,9 +251,15 @@ public:
static bool RunScriptResponse( CBaseEntity *pTarget, const char *response, AI_CriteriaSet *criteria, bool file );
#endif
#ifdef MAPBASE
public:
#else
protected:
#endif
CAI_TimedSemaphore *GetMySpeechSemaphore( CBaseEntity *pNpc );
protected:
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
#ifdef MAPBASE
@ -311,11 +324,15 @@ private:
//
template <class BASE_NPC>
class CAI_ExpresserHost : public BASE_NPC, protected CAI_ExpresserSink
class CAI_ExpresserHost : public BASE_NPC, public CAI_ExpresserSink
{
DECLARE_CLASS_NOFRIEND( CAI_ExpresserHost, BASE_NPC );
public:
#ifdef MAPBASE
CAI_Expresser *GetSinkExpresser() { return this->GetExpresser(); }
#endif
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 );

View File

@ -11,6 +11,9 @@
#include "ai_baseactor.h"
#include "ai_speech.h"
//#include "flex_expresser.h"
#ifdef MAPBASE
#include "sceneentity.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
@ -170,15 +173,25 @@ void CResponseQueue::RemoveExpresserHost(CBaseEntity *host)
}
}
#ifdef MAPBASE
/// Get the expresser for a base entity.
static CAI_Expresser *InferExpresserFromBaseEntity(CBaseEntity * RESTRICT pEnt, CAI_ExpresserSink **ppSink = NULL)
{
if ( CAI_ExpresserSink *pSink = dynamic_cast<CAI_ExpresserSink *>(pEnt) )
{
if (ppSink)
*ppSink = pSink;
return pSink->GetSinkExpresser();
}
return NULL;
}
#else
/// 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)
{
#ifdef MAPBASE
if ( CBasePlayer *pPlayer = ToBasePlayer(pEnt) )
#else
if ( CBaseMultiplayerPlayer *pPlayer = dynamic_cast<CBaseMultiplayerPlayer *>(pEnt) )
#endif
{
return pPlayer->GetExpresser();
}
@ -197,6 +210,7 @@ static CAI_Expresser *InferExpresserFromBaseEntity(CBaseEntity * RESTRICT pEnt)
return NULL;
}
}
#endif
void CResponseQueue::CDeferredResponse::Quash()
@ -205,6 +219,23 @@ void CResponseQueue::CDeferredResponse::Quash()
m_fDispatchTime = 0;
}
#ifdef MAPBASE
void CResponseQueue::AppendFollowupCriteria( AIConcept_t concept, AI_CriteriaSet &set, CAI_Expresser *pEx,
CAI_ExpresserSink *pSink, CBaseEntity *pTarget, CBaseEntity *pIssuer, DeferredResponseTarget_t nTargetType )
{
// Allows control over which followups interrupt speech routines
set.AppendCriteria( "followup_allowed_to_speak", (pSink->IsAllowedToSpeakFollowup( concept, pIssuer, nTargetType == kDRT_SPECIFIC )) ? "1" : "0" );
set.AppendCriteria( "followup_target_type", UTIL_VarArgs( "%i", (int)nTargetType ) );
// NOTE: This assumes any expresser entity derived from CBaseFlex is also derived from CBaseCombatCharacter
if (pTarget->IsCombatCharacter())
set.AppendCriteria( "is_speaking", (pEx->IsSpeaking() || IsRunningScriptedSceneWithSpeechAndNotPaused( assert_cast<CBaseFlex*>(pTarget) )) ? "1" : "0" );
else
set.AppendCriteria( "is_speaking", "0" );
}
#endif
bool CResponseQueue::DispatchOneResponse(CDeferredResponse &response)
{
// find the target.
@ -272,9 +303,15 @@ bool CResponseQueue::DispatchOneResponse(CDeferredResponse &response)
continue; // too far
}
#ifdef MAPBASE
CAI_ExpresserSink *pSink = NULL;
pEx = InferExpresserFromBaseEntity( pTarget, &pSink );
#else
pEx = InferExpresserFromBaseEntity(pTarget);
#endif
if ( !pEx || pTarget == pIssuer )
continue;
AI_CriteriaSet characterCriteria;
pEx->GatherCriteria(&characterCriteria, response.m_concept, NULL);
characterCriteria.Merge(&deferredCriteria);
@ -282,6 +319,11 @@ bool CResponseQueue::DispatchOneResponse(CDeferredResponse &response)
{
characterCriteria.AppendCriteria( "dist_from_issuer", UTIL_VarArgs( "%f", sqrt(distIssuerToTargetSq) ) );
}
#ifdef MAPBASE
AppendFollowupCriteria( response.m_concept, characterCriteria, pEx, pSink, pTarget, pIssuer, kDRT_ALL );
#endif
AI_Response prospectiveResponse;
if ( pEx->FindResponse( prospectiveResponse, response.m_concept, &characterCriteria ) )
{
@ -304,14 +346,26 @@ bool CResponseQueue::DispatchOneResponse(CDeferredResponse &response)
return false; // we're done right here.
// Get the expresser for the target.
#ifdef MAPBASE
CAI_ExpresserSink *pSink = NULL;
pEx = InferExpresserFromBaseEntity( pTarget, &pSink );
#else
pEx = InferExpresserFromBaseEntity(pTarget);
#endif
if (!pEx)
return false;
AI_CriteriaSet characterCriteria;
pEx->GatherCriteria(&characterCriteria, response.m_concept, NULL);
characterCriteria.Merge(&deferredCriteria);
#ifdef MAPBASE
if ( pIssuer )
{
characterCriteria.AppendCriteria( "dist_from_issuer", UTIL_VarArgs( "%f", (pTarget->GetAbsOrigin() - pIssuer->GetAbsOrigin()).Length() ) );
}
AppendFollowupCriteria( response.m_concept, characterCriteria, pEx, pSink, pTarget, pIssuer, kDRT_SPECIFIC );
#endif
pEx->Speak( response.m_concept, &characterCriteria );
return true;
@ -364,7 +418,12 @@ bool CResponseQueue::DispatchOneResponse_ThenANY( CDeferredResponse &response, A
continue; // too far
}
#ifdef MAPBASE
CAI_ExpresserSink *pSink = NULL;
pEx = InferExpresserFromBaseEntity( pTarget, &pSink );
#else
pEx = InferExpresserFromBaseEntity(pTarget);
#endif
if ( !pEx )
continue;
@ -376,6 +435,11 @@ bool CResponseQueue::DispatchOneResponse_ThenANY( CDeferredResponse &response, A
{
characterCriteria.AppendCriteria( "dist_from_issuer", UTIL_VarArgs( "%f", sqrt(distIssuerToTargetSq) ) );
}
#ifdef MAPBASE
AppendFollowupCriteria( response.m_concept, characterCriteria, pEx, pSink, pTarget, pIssuer, kDRT_ANY );
#endif
AI_Response prospectiveResponse;
#ifdef MAPBASE

View File

@ -116,6 +116,11 @@ public:
inline int GetNumExpresserTargets() const;
inline CBaseEntity *GetExpresserHost(int which) const;
#ifdef MAPBASE
void AppendFollowupCriteria( AIConcept_t concept, AI_CriteriaSet &set, CAI_Expresser *pEx,
CAI_ExpresserSink *pSink, CBaseEntity *pTarget, CBaseEntity *pIssuer, DeferredResponseTarget_t nTargetType );
#endif
protected:
/// Actually send off one response to a consumer
/// Return true if dispatch succeeded