mirror of
https://github.com/mapbase-source/source-sdk-2013.git
synced 2025-01-14 15:57:59 +03:00
Made followup responses more reliable with generic NPCs and added "vscript_file" response type
This commit is contained in:
parent
d6b959899c
commit
d4a91fe027
@ -74,6 +74,10 @@ static void DispatchComeback( CAI_ExpresserWithFollowup *pExpress, CBaseEntity *
|
||||
// add in the FROM context so dispatchee knows was from me
|
||||
const char * RESTRICT pszSpeakerName = GetResponseName( pSpeaker );
|
||||
criteria.AppendCriteria( "From", pszSpeakerName );
|
||||
#ifdef MAPBASE
|
||||
// See DispatchFollowupThroughQueue()
|
||||
criteria.AppendCriteria( "From_idx", CNumStr( pSpeaker->entindex() ) );
|
||||
#endif
|
||||
// if a SUBJECT criteria is missing, put it back in.
|
||||
if ( criteria.FindCriterionIndex( "Subject" ) == -1 )
|
||||
{
|
||||
@ -140,8 +144,17 @@ static CBaseEntity *AscertainSpeechSubjectFromContext( AI_Response *response, AI
|
||||
const char *subject = criteria.GetValue( criteria.FindCriterionIndex( pContextName ) );
|
||||
if (subject)
|
||||
{
|
||||
CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, subject );
|
||||
|
||||
return gEntList.FindEntityByName( NULL, subject );
|
||||
#ifdef MAPBASE
|
||||
// Allow entity indices to be used (see DispatchFollowupThroughQueue() for one particular use case)
|
||||
if (!pEnt && atoi(subject))
|
||||
{
|
||||
pEnt = CBaseEntity::Instance( atoi( subject ) );
|
||||
}
|
||||
#endif
|
||||
|
||||
return pEnt;
|
||||
|
||||
}
|
||||
else
|
||||
@ -166,7 +179,12 @@ static CResponseQueue::CFollowupTargetSpec_t ResolveFollowupTargetToEntity( AICo
|
||||
}
|
||||
else if ( Q_stricmp(szTarget, "from") == 0 )
|
||||
{
|
||||
#ifdef MAPBASE
|
||||
// See DispatchFollowupThroughQueue()
|
||||
return CResponseQueue::CFollowupTargetSpec_t( AscertainSpeechSubjectFromContext( response, criteria, "From_idx" ) );
|
||||
#else
|
||||
return CResponseQueue::CFollowupTargetSpec_t( AscertainSpeechSubjectFromContext( response, criteria, "From" ) );
|
||||
#endif
|
||||
}
|
||||
else if ( Q_stricmp(szTarget, "any") == 0 )
|
||||
{
|
||||
@ -400,6 +418,14 @@ void CAI_ExpresserWithFollowup::DispatchFollowupThroughQueue( const AIConcept_t
|
||||
// Don't add my own criteria! GatherCriteria( &criteria, followup.followup_concept, followup.followup_contexts );
|
||||
|
||||
criteria.AppendCriteria( "From", STRING( pOuter->GetEntityName() ) );
|
||||
#ifdef MAPBASE
|
||||
// The index of the "From" entity.
|
||||
// In HL2 mods, many followup users would be generic NPCs (e.g. citizens) who might not have any particular significance.
|
||||
// Those generic NPCs are quite likely to have no name or have a name in common with other entities. As a result, Mapbase
|
||||
// changes internal operations of the "From" context to search for an entity index. This won't be 100% reliable if the source
|
||||
// talker dies and another entity is created immediately afterwards, but it's a lot more reliable than a simple entity name search.
|
||||
criteria.AppendCriteria( "From_idx", CNumStr( pOuter->entindex() ) );
|
||||
#endif
|
||||
|
||||
criteria.Merge( criteriaStr );
|
||||
g_ResponseQueueManager.GetQueue()->Add( concept, &criteria, gpGlobals->curtime + delay, target, pOuter );
|
||||
@ -436,6 +462,12 @@ void CAI_ExpresserWithFollowup::OnSpeechFinished()
|
||||
{
|
||||
if (m_pPostponedFollowup && m_pPostponedFollowup->IsValid())
|
||||
{
|
||||
#ifdef MAPBASE
|
||||
// HACKHACK: Non-scene speech (e.g. noscene speak/sentence) fire OnSpeechFinished() immediately,
|
||||
// so add the actual speech time to the followup delay
|
||||
if (GetTimeSpeechCompleteWithoutDelay() > gpGlobals->curtime)
|
||||
m_pPostponedFollowup->followup_delay += GetTimeSpeechCompleteWithoutDelay() - gpGlobals->curtime;
|
||||
#endif
|
||||
return SpeakDispatchFollowup(*m_pPostponedFollowup);
|
||||
}
|
||||
}
|
||||
|
@ -750,6 +750,10 @@ bool CAI_Expresser::SpeakDispatchResponse( AIConcept_t &concept, AI_Response *re
|
||||
DevMsg( 2, "SpeakDispatchResponse: Entity ( %i/%s ) playing sound '%s'\n", GetOuter()->entindex(), STRING( GetOuter()->GetEntityName() ), response );
|
||||
NoteSpeaking( speakTime, delay );
|
||||
spoke = true;
|
||||
#ifdef MAPBASE
|
||||
// Not really any other way of doing this
|
||||
OnSpeechFinished();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -757,6 +761,10 @@ bool CAI_Expresser::SpeakDispatchResponse( AIConcept_t &concept, AI_Response *re
|
||||
case ResponseRules::RESPONSE_SENTENCE:
|
||||
{
|
||||
spoke = ( -1 != SpeakRawSentence( response, delay, VOL_NORM, soundlevel ) ) ? true : false;
|
||||
#ifdef MAPBASE
|
||||
// Not really any other way of doing this
|
||||
OnSpeechFinished();
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
@ -781,17 +789,30 @@ bool CAI_Expresser::SpeakDispatchResponse( AIConcept_t &concept, AI_Response *re
|
||||
NDebugOverlay::Text( vPrintPos, response, true, 1.5 );
|
||||
}
|
||||
spoke = true;
|
||||
#ifdef MAPBASE
|
||||
OnSpeechFinished();
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
case ResponseRules::RESPONSE_ENTITYIO:
|
||||
{
|
||||
return FireEntIOFromResponse( response, GetOuter() );
|
||||
spoke = FireEntIOFromResponse( response, GetOuter() );
|
||||
#ifdef MAPBASE
|
||||
OnSpeechFinished();
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
#ifdef MAPBASE
|
||||
#ifdef MAPBASE_VSCRIPT
|
||||
case ResponseRules::RESPONSE_VSCRIPT:
|
||||
{
|
||||
return GetOuter()->RunScript( response, "ResponseScript" );
|
||||
spoke = RunScriptResponse( GetOuter(), response, criteria, false );
|
||||
OnSpeechFinished();
|
||||
}
|
||||
break;
|
||||
case ResponseRules::RESPONSE_VSCRIPT_FILE:
|
||||
{
|
||||
spoke = RunScriptResponse( GetOuter(), response, criteria, true );
|
||||
OnSpeechFinished();
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
@ -918,6 +939,47 @@ bool CAI_Expresser::FireEntIOFromResponse( char *response, CBaseEntity *pInitiat
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef MAPBASE_VSCRIPT
|
||||
bool CAI_Expresser::RunScriptResponse( CBaseEntity *pTarget, const char *response, AI_CriteriaSet *criteria, bool file )
|
||||
{
|
||||
if (!pTarget->ValidateScriptScope())
|
||||
return false;
|
||||
|
||||
ScriptVariant_t varCriteriaTable;
|
||||
g_pScriptVM->CreateTable( varCriteriaTable );
|
||||
|
||||
if (criteria)
|
||||
{
|
||||
// Sort all of the criteria into a table.
|
||||
// Letting VScript have access to this is important because not all criteria is appended in ModifyOrAppendCriteria() and
|
||||
// not all contexts are actually appended as contexts. This is specifically important for followup responses.
|
||||
int count = criteria->GetCount();
|
||||
for ( int i = 0 ; i < count ; ++i )
|
||||
{
|
||||
// TODO: Weight?
|
||||
g_pScriptVM->SetValue( varCriteriaTable, criteria->GetName(i), criteria->GetValue(i) );
|
||||
}
|
||||
|
||||
g_pScriptVM->SetValue( "criteria", varCriteriaTable );
|
||||
}
|
||||
|
||||
bool bSuccess = false;
|
||||
if (file)
|
||||
{
|
||||
bSuccess = pTarget->RunScriptFile( response );
|
||||
}
|
||||
else
|
||||
{
|
||||
bSuccess = pTarget->RunScript( response, "ResponseScript" );
|
||||
}
|
||||
|
||||
g_pScriptVM->ClearValue( "criteria" );
|
||||
g_pScriptVM->ReleaseScript( varCriteriaTable );
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Input : *response -
|
||||
@ -967,6 +1029,37 @@ float CAI_Expresser::GetResponseDuration( AI_Response *result )
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
#ifdef MAPBASE
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_Expresser::SetUsingProspectiveResponses( bool bToggle )
|
||||
{
|
||||
VPROF("CAI_Expresser::SetUsingProspectiveResponses");
|
||||
IResponseSystem *rs = GetOuter()->GetResponseSystem();
|
||||
if ( !rs )
|
||||
{
|
||||
Assert( !"No response system installed for CAI_Expresser::GetOuter()!!!" );
|
||||
return;
|
||||
}
|
||||
|
||||
rs->SetProspective( bToggle );
|
||||
}
|
||||
|
||||
void CAI_Expresser::MarkResponseAsUsed( AI_Response *response )
|
||||
{
|
||||
VPROF("CAI_Expresser::MarkResponseAsUsed");
|
||||
IResponseSystem *rs = GetOuter()->GetResponseSystem();
|
||||
if ( !rs )
|
||||
{
|
||||
Assert( !"No response system installed for CAI_Expresser::GetOuter()!!!" );
|
||||
return;
|
||||
}
|
||||
|
||||
rs->MarkResponseAsUsed( response->GetInternalIndices()[0], response->GetInternalIndices()[1] );
|
||||
}
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Placeholder for rules based response system
|
||||
// Input : concept -
|
||||
|
@ -178,6 +178,11 @@ public:
|
||||
virtual bool SpeakDispatchResponse( AIConcept_t &concept, AI_Response *response, AI_CriteriaSet *criteria, IRecipientFilter *filter = NULL );
|
||||
float GetResponseDuration( AI_Response *response );
|
||||
|
||||
#ifdef MAPBASE
|
||||
void SetUsingProspectiveResponses( bool bToggle );
|
||||
void MarkResponseAsUsed( AI_Response *response );
|
||||
#endif
|
||||
|
||||
virtual int SpeakRawSentence( const char *pszSentence, float delay, float volume = VOL_NORM, soundlevel_t soundlevel = SNDLVL_TALKING, CBaseEntity *pListener = NULL );
|
||||
|
||||
bool SemaphoreIsAvailable( CBaseEntity *pTalker );
|
||||
@ -194,6 +199,9 @@ public:
|
||||
bool CanSpeak();
|
||||
bool CanSpeakAfterMyself();
|
||||
float GetTimeSpeechComplete() const { return m_flStopTalkTime; }
|
||||
#ifdef MAPBASE
|
||||
float GetTimeSpeechCompleteWithoutDelay() const { return m_flStopTalkTimeWithoutDelay; }
|
||||
#endif
|
||||
void BlockSpeechUntil( float time );
|
||||
|
||||
// --------------------------------
|
||||
@ -227,6 +235,11 @@ public:
|
||||
// returns false on failure (eg, couldn't match parse contents)
|
||||
static bool FireEntIOFromResponse( char *response, CBaseEntity *pInitiator );
|
||||
|
||||
#ifdef MAPBASE_VSCRIPT
|
||||
// Used for RESPONSE_VSCRIPT(_FILE)
|
||||
static bool RunScriptResponse( CBaseEntity *pTarget, const char *response, AI_CriteriaSet *criteria, bool file );
|
||||
#endif
|
||||
|
||||
protected:
|
||||
CAI_TimedSemaphore *GetMySpeechSemaphore( CBaseEntity *pNpc );
|
||||
|
||||
|
@ -374,6 +374,10 @@ bool CResponseQueue::DispatchOneResponse_ThenANY( CDeferredResponse &response, A
|
||||
}
|
||||
AI_Response prospectiveResponse;
|
||||
|
||||
#ifdef MAPBASE
|
||||
pEx->SetUsingProspectiveResponses( true );
|
||||
#endif
|
||||
|
||||
if ( pEx->FindResponse( prospectiveResponse, response.m_concept, &characterCriteria ) )
|
||||
{
|
||||
float score = prospectiveResponse.GetMatchScore();
|
||||
@ -391,7 +395,12 @@ bool CResponseQueue::DispatchOneResponse_ThenANY( CDeferredResponse &response, A
|
||||
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 )
|
||||
{
|
||||
#ifdef MAPBASE
|
||||
pEx->SetUsingProspectiveResponses( false );
|
||||
#endif
|
||||
continue; // SAFETY: don't overflow the array
|
||||
}
|
||||
|
||||
responseToSay[numExFound] = prospectiveResponse;
|
||||
pBestEx[numExFound] = pEx;
|
||||
@ -400,6 +409,10 @@ bool CResponseQueue::DispatchOneResponse_ThenANY( CDeferredResponse &response, A
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MAPBASE
|
||||
pEx->SetUsingProspectiveResponses( false );
|
||||
#endif
|
||||
}
|
||||
|
||||
// if I have a response, dispatch it.
|
||||
@ -410,6 +423,9 @@ bool CResponseQueue::DispatchOneResponse_ThenANY( CDeferredResponse &response, A
|
||||
|
||||
if ( pBestEx[iSelect] != NULL )
|
||||
{
|
||||
#ifdef MAPBASE
|
||||
pBestEx[iSelect]->MarkResponseAsUsed( responseToSay + iSelect );
|
||||
#endif
|
||||
return pBestEx[iSelect]->SpeakDispatchResponse( response.m_concept, responseToSay + iSelect, pDeferredCriteria );
|
||||
}
|
||||
else
|
||||
|
@ -98,6 +98,7 @@ namespace ResponseRules
|
||||
RESPONSE_ENTITYIO, // poke an input on an entity
|
||||
#ifdef MAPBASE
|
||||
RESPONSE_VSCRIPT, // Run VScript code
|
||||
RESPONSE_VSCRIPT_FILE, // Run a VScript file (bypasses ugliness and character limits when just using IncludeScript() with RESPONSE_VSCRIPT)
|
||||
#endif
|
||||
|
||||
NUM_RESPONSES,
|
||||
@ -351,6 +352,9 @@ namespace ResponseRules
|
||||
#ifdef MAPBASE
|
||||
int GetContextFlags() { return m_iContextFlags; }
|
||||
bool IsApplyContextToWorld( void ) { return (m_iContextFlags & APPLYCONTEXT_WORLD) != 0; }
|
||||
|
||||
inline short *GetInternalIndices() { return m_InternalIndices; }
|
||||
inline void SetInternalIndices( short iGroup, short iWithinGroup ) { m_InternalIndices[0] = iGroup; m_InternalIndices[1] = iWithinGroup; }
|
||||
#else
|
||||
bool IsApplyContextToWorld( void ) { return m_bApplyContextToWorld; }
|
||||
#endif
|
||||
@ -393,6 +397,10 @@ namespace ResponseRules
|
||||
char * m_szContext; // context data we apply to character after running
|
||||
#ifdef MAPBASE
|
||||
int m_iContextFlags;
|
||||
|
||||
// The response's original indices in the system. [0] is the group's index, [1] is the index within the group.
|
||||
// For now, this is only set in prospecctive mode. It's used to call back to the ParserResponse and mark a prospectively chosen response as used.
|
||||
short m_InternalIndices[2];
|
||||
#else
|
||||
bool m_bApplyContextToWorld;
|
||||
#endif
|
||||
@ -418,6 +426,15 @@ namespace ResponseRules
|
||||
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;
|
||||
|
||||
#ifdef MAPBASE
|
||||
// (Optional) Call this before and after using FindBestResponse() for a prospective lookup, e.g. a response that might not actually be used
|
||||
// and should not trigger displayfirst, etc.
|
||||
virtual void SetProspective( bool bToggle ) {};
|
||||
|
||||
// (Optional) Marks a prospective response as used
|
||||
virtual void MarkResponseAsUsed( short iGroup, short iWithinGroup ) {};
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
|
@ -942,6 +942,10 @@ int CResponseSystem::SelectWeightedResponseFromResponseGroup( ResponseGroup *g,
|
||||
}
|
||||
|
||||
if ( slot != -1 )
|
||||
#ifdef MAPBASE
|
||||
// Don't mark responses as used in prospective mode
|
||||
if (m_bInProspective == false)
|
||||
#endif
|
||||
g->MarkResponseUsed( slot );
|
||||
|
||||
// Revert fake depletion of unavailable choices
|
||||
@ -1284,6 +1288,26 @@ bool CResponseSystem::FindBestResponse( const CriteriaSet& set, CRR_Response& re
|
||||
context = r->GetContext();
|
||||
#ifdef MAPBASE
|
||||
contextflags = r->GetContextFlags();
|
||||
|
||||
// Sets the internal indices for the response to call back to later for prospective responses
|
||||
// (NOTE: Performance not tested; Be wary of turning off the m_bInProspective check!)
|
||||
if (m_bInProspective)
|
||||
{
|
||||
for ( int i = 0; i < (int)m_Responses.Count(); i++ )
|
||||
{
|
||||
if (&m_Responses[i] == result.group)
|
||||
{
|
||||
ResponseGroup &group = m_Responses[i];
|
||||
for ( int j = 0; j < group.group.Count(); j++)
|
||||
{
|
||||
if (&group.group[j] == result.action)
|
||||
{
|
||||
response.SetInternalIndices( i, j );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
bcontexttoworld = r->IsApplyContextToWorld();
|
||||
#endif
|
||||
@ -1369,6 +1393,22 @@ void CResponseSystem::GetAllResponses( CUtlVector<CRR_Response> *pResponses )
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MAPBASE
|
||||
void CResponseSystem::MarkResponseAsUsed( short iGroup, short iWithinGroup )
|
||||
{
|
||||
if (m_Responses.Count() > (unsigned int)iGroup)
|
||||
{
|
||||
ResponseGroup &group = m_Responses[iGroup];
|
||||
if (group.group.Count() > (int)iWithinGroup)
|
||||
{
|
||||
group.MarkResponseUsed( iWithinGroup );
|
||||
|
||||
Msg("Marked response %s (%i) used\n", group.group[iWithinGroup].value, iWithinGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void CResponseSystem::ParseInclude()
|
||||
{
|
||||
char includefile[ 256 ];
|
||||
@ -1521,7 +1561,10 @@ inline ResponseType_t ComputeResponseType( const char *s )
|
||||
return RESPONSE_ENTITYIO;
|
||||
#ifdef MAPBASE
|
||||
case 'v':
|
||||
return RESPONSE_VSCRIPT;
|
||||
if (*(s + 7) == '_')
|
||||
return RESPONSE_VSCRIPT_FILE;
|
||||
else
|
||||
return RESPONSE_VSCRIPT;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,12 @@ namespace ResponseRules
|
||||
// IResponseSystem
|
||||
virtual bool FindBestResponse( const CriteriaSet& set, CRR_Response& response, IResponseFilter *pFilter = NULL );
|
||||
virtual void GetAllResponses( CUtlVector<CRR_Response> *pResponses );
|
||||
|
||||
#ifdef MAPBASE
|
||||
virtual void SetProspective( bool bToggle ) { m_bInProspective = bToggle; }
|
||||
|
||||
virtual void MarkResponseAsUsed( short iGroup, short iWithinGroup );
|
||||
#endif
|
||||
#pragma endregion Implement interface from IResponseSystem
|
||||
|
||||
virtual void Release() = 0;
|
||||
@ -283,6 +289,13 @@ public:
|
||||
|
||||
bool m_bCustomManagable;
|
||||
|
||||
#ifdef MAPBASE
|
||||
// This is a hack specifically designed to fix displayfirst, speakonce, etc. in "prospective" response searches,
|
||||
// especially the prospective lookups in followup responses.
|
||||
// It works by preventing responses from being marked as "used".
|
||||
bool m_bInProspective;
|
||||
#endif
|
||||
|
||||
struct ScriptEntry
|
||||
{
|
||||
unsigned char *buffer;
|
||||
|
@ -244,6 +244,8 @@ const char *CRR_Response::DescribeResponse( ResponseType_t type )
|
||||
#ifdef MAPBASE
|
||||
case ResponseRules::RESPONSE_VSCRIPT:
|
||||
return "RESPONSE_VSCRIPT";
|
||||
case ResponseRules::RESPONSE_VSCRIPT_FILE:
|
||||
return "RESPONSE_VSCRIPT_FILE";
|
||||
#endif
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user