Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions sp/src/game/server/ai_expresserfollowup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -99,6 +108,7 @@ static void DispatchComeback( CAI_ExpresserWithFollowup *pExpress, CBaseEntity *
{
pActor->Speak( followup.followup_concept, &criteria );
}
#endif
}

#if 0
Expand Down
48 changes: 48 additions & 0 deletions sp/src/game/server/ai_playerally.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do followups get around the semaphore?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see below that if the follow up specifically targets this NPC, it ignores the semaphore. That makes sense. I wonder if there would ever be cases where designers would not want this to be the case? Like low priority followup responses


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 )
Expand Down
3 changes: 3 additions & 0 deletions sp/src/game/server/ai_playerally.h
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,9 @@ class CAI_PlayerAlly : public CAI_BaseActor

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 );
Expand Down
19 changes: 18 additions & 1 deletion sp/src/game/server/ai_speech_new.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ class CAI_ExpresserSink
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
Expand Down Expand Up @@ -244,9 +251,15 @@ class CAI_Expresser : public ResponseRules::IResponseFilter
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
Expand Down Expand Up @@ -311,11 +324,15 @@ class CAI_Expresser : public ResponseRules::IResponseFilter
//

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 );
Expand Down
74 changes: 69 additions & 5 deletions sp/src/game/server/ai_speechqueue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -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();
}
Expand All @@ -197,6 +210,7 @@ static CAI_Expresser *InferExpresserFromBaseEntity(CBaseEntity * RESTRICT pEnt)
return NULL;
}
}
#endif


void CResponseQueue::CDeferredResponse::Quash()
Expand All @@ -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.
Expand Down Expand Up @@ -272,16 +303,27 @@ 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);
if ( pIssuer )
{
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 ) )
{
Expand All @@ -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;
Expand Down Expand Up @@ -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;

Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions sp/src/game/server/ai_speechqueue.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ class CResponseQueue
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
Expand Down