From 4adab159821c53a96e1cf19861e5e572f294d1ad Mon Sep 17 00:00:00 2001 From: "ALLEN-PC\\acj30" Date: Sat, 6 Jan 2024 13:00:41 -0600 Subject: [PATCH] Add better circumstantial criteria to followup responses --- sp/src/game/server/ai_expresserfollowup.cpp | 10 +++ sp/src/game/server/ai_playerally.cpp | 48 +++++++++++++ sp/src/game/server/ai_playerally.h | 3 + sp/src/game/server/ai_speech_new.h | 19 +++++- sp/src/game/server/ai_speechqueue.cpp | 74 +++++++++++++++++++-- sp/src/game/server/ai_speechqueue.h | 5 ++ 6 files changed, 153 insertions(+), 6 deletions(-) diff --git a/sp/src/game/server/ai_expresserfollowup.cpp b/sp/src/game/server/ai_expresserfollowup.cpp index 65575709..38a83a3d 100644 --- a/sp/src/game/server/ai_expresserfollowup.cpp +++ b/sp/src/game/server/ai_expresserfollowup.cpp @@ -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(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(pRespondent)) @@ -99,6 +108,7 @@ static void DispatchComeback( CAI_ExpresserWithFollowup *pExpress, CBaseEntity * { pActor->Speak( followup.followup_concept, &criteria ); } +#endif } #if 0 diff --git a/sp/src/game/server/ai_playerally.cpp b/sp/src/game/server/ai_playerally.cpp index f7594feb..b264c74a 100644 --- a/sp/src/game/server/ai_playerally.cpp +++ b/sp/src/game/server/ai_playerally.cpp @@ -1762,6 +1762,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 ) diff --git a/sp/src/game/server/ai_playerally.h b/sp/src/game/server/ai_playerally.h index 93448ec7..7ab7f9b2 100644 --- a/sp/src/game/server/ai_playerally.h +++ b/sp/src/game/server/ai_playerally.h @@ -392,6 +392,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 ); diff --git a/sp/src/game/server/ai_speech_new.h b/sp/src/game/server/ai_speech_new.h index c61f6f03..8290d471 100644 --- a/sp/src/game/server/ai_speech_new.h +++ b/sp/src/game/server/ai_speech_new.h @@ -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 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 ); diff --git a/sp/src/game/server/ai_speechqueue.cpp b/sp/src/game/server/ai_speechqueue.cpp index 7e8bf055..58fccde6 100644 --- a/sp/src/game/server/ai_speechqueue.cpp +++ b/sp/src/game/server/ai_speechqueue.cpp @@ -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 @@ -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(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(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(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 diff --git a/sp/src/game/server/ai_speechqueue.h b/sp/src/game/server/ai_speechqueue.h index 15101b70..87ab064f 100644 --- a/sp/src/game/server/ai_speechqueue.h +++ b/sp/src/game/server/ai_speechqueue.h @@ -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