NPCs can load dynamic interactions from all animation MDLs

This commit is contained in:
Peter Covington 2022-04-15 09:59:17 -04:00
parent f8a8d49be7
commit f9d88b15ac

View File

@ -14719,48 +14719,58 @@ bool CAI_BaseNPC::IsAllowedToDodge( void )
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_BaseNPC::ParseScriptedNPCInteractions( void )
void CAI_BaseNPC::ParseScriptedNPCInteractions(void)
{
// Already parsed them?
if ( m_ScriptedInteractions.Count() )
if (m_ScriptedInteractions.Count())
return;
// Parse the model's key values and find any dynamic interactions
KeyValues *modelKeyValues = new KeyValues("");
CUtlBuffer buf( 1024, 0, CUtlBuffer::TEXT_BUFFER );
KeyValues* modelKeyValues = new KeyValues("");
CUtlBuffer buf(1024, 0, CUtlBuffer::TEXT_BUFFER);
if (! modelinfo->GetModelKeyValue( GetModel(), buf ))
if (!modelinfo->GetModelKeyValue(GetModel(), buf))
return;
if ( modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), buf ) )
if (modelKeyValues->LoadFromBuffer(modelinfo->GetModelName(GetModel()), buf))
{
#ifdef MAPBASE
CUtlVector<string_t> iszUsedNames;
for (KeyValues* pkvModelBlock = modelKeyValues; pkvModelBlock != nullptr; pkvModelBlock = pkvModelBlock->GetNextKey())
{
// Do we have a dynamic interactions section?
KeyValues *pkvInteractions = modelKeyValues->FindKey("dynamic_interactions");
if ( pkvInteractions )
KeyValues* pkvInteractions = pkvModelBlock->FindKey("dynamic_interactions");
if (pkvInteractions)
{
KeyValues *pkvNode = pkvInteractions->GetFirstSubKey();
while ( pkvNode )
KeyValues* pkvNode = pkvInteractions->GetFirstSubKey();
while (pkvNode)
{
ScriptedNPCInteraction_t sInteraction;
sInteraction.iszInteractionName = AllocPooledString( pkvNode->GetName() );
sInteraction.iszInteractionName = AllocPooledString(pkvNode->GetName());
if (iszUsedNames.Find(sInteraction.iszInteractionName) != iszUsedNames.InvalidIndex())
{
DevMsg(2, "Scripted interaction %s already defined on %s\n", pkvNode->GetName(), GetClassname());
pkvNode = pkvNode->GetNextKey();
continue;
}
#ifdef MAPBASE
// The method for parsing dynamic interactions has been reworked.
// Unknown values are now stored as response contexts to test against response criteria.
bool bValidInteraction = true;
// Default values
UTIL_StringToVector( sInteraction.vecRelativeOrigin.Base(), "0 0 0" );
UTIL_StringToVector(sInteraction.vecRelativeOrigin.Base(), "0 0 0");
sInteraction.flDelay = 10.0;
sInteraction.flDistSqr = (DSS_MAX_DIST * DSS_MAX_DIST);
// Misc. response criteria
char *szCriteria = "";
char* szCriteria = "";
KeyValues *pCurNode = pkvNode->GetFirstSubKey();
const char *szName = NULL;
const char *szValue = NULL;
KeyValues* pCurNode = pkvNode->GetFirstSubKey();
const char* szName = NULL;
const char* szValue = NULL;
while (pCurNode)
{
szName = pCurNode->GetName();
@ -14796,54 +14806,52 @@ void CAI_BaseNPC::ParseScriptedNPCInteractions( void )
else if (!Q_strncmp(szValue, "loop_break_trigger", 18))
{
char szTrigger[256];
Q_strncpy( szTrigger, szValue, sizeof(szTrigger) );
char *pszParam = strtok( szTrigger, " " );
Q_strncpy(szTrigger, szValue, sizeof(szTrigger));
char* pszParam = strtok(szTrigger, " ");
while (pszParam)
{
if ( !Q_strncmp( pszParam, "on_damage", 9) )
if (!Q_strncmp(pszParam, "on_damage", 9))
{
sInteraction.iLoopBreakTriggerMethod |= SNPCINT_LOOPBREAK_ON_DAMAGE;
}
else if ( !Q_strncmp( pszParam, "on_flashlight_illum", 19) )
else if (!Q_strncmp(pszParam, "on_flashlight_illum", 19))
{
sInteraction.iLoopBreakTriggerMethod |= SNPCINT_LOOPBREAK_ON_FLASHLIGHT_ILLUM;
}
pszParam = strtok(NULL," ");
pszParam = strtok(NULL, " ");
}
}
else if (!Q_strncmp(szName, "origin_relative", 15))
UTIL_StringToVector( sInteraction.vecRelativeOrigin.Base(), szValue );
UTIL_StringToVector(sInteraction.vecRelativeOrigin.Base(), szValue);
else if (!Q_strncmp(szName, "angles_relative", 15))
{
sInteraction.iFlags |= SCNPC_FLAG_TEST_OTHER_ANGLES;
UTIL_StringToVector( sInteraction.angRelativeAngles.Base(), szValue );
UTIL_StringToVector(sInteraction.angRelativeAngles.Base(), szValue);
}
else if (!Q_strncmp(szName, "velocity_relative", 17))
{
sInteraction.iFlags |= SCNPC_FLAG_TEST_OTHER_VELOCITY;
UTIL_StringToVector( sInteraction.vecRelativeVelocity.Base(), szValue );
UTIL_StringToVector(sInteraction.vecRelativeVelocity.Base(), szValue);
}
#ifdef MAPBASE
else if (!Q_strncmp(szName, "end_position", 12))
{
sInteraction.iFlags |= SCNPC_FLAG_TEST_END_POSITION;
UTIL_StringToVector( sInteraction.vecRelativeEndPos.Base(), szValue );
UTIL_StringToVector(sInteraction.vecRelativeEndPos.Base(), szValue);
}
#endif
else if (!Q_strncmp(szName, "entry_sequence", 14))
sInteraction.sPhases[SNPCINT_ENTRY].iszSequence = AllocPooledString( szValue );
sInteraction.sPhases[SNPCINT_ENTRY].iszSequence = AllocPooledString(szValue);
else if (!Q_strncmp(szName, "entry_activity", 14))
sInteraction.sPhases[SNPCINT_ENTRY].iActivity = GetActivityID( szValue );
sInteraction.sPhases[SNPCINT_ENTRY].iActivity = GetActivityID(szValue);
else if (!Q_strncmp(szName, "sequence", 8))
sInteraction.sPhases[SNPCINT_SEQUENCE].iszSequence = AllocPooledString( szValue );
sInteraction.sPhases[SNPCINT_SEQUENCE].iszSequence = AllocPooledString(szValue);
else if (!Q_strncmp(szName, "activity", 8))
sInteraction.sPhases[SNPCINT_SEQUENCE].iActivity = GetActivityID( szValue );
sInteraction.sPhases[SNPCINT_SEQUENCE].iActivity = GetActivityID(szValue);
else if (!Q_strncmp(szName, "exit_sequence", 13))
sInteraction.sPhases[SNPCINT_EXIT].iszSequence = AllocPooledString( szValue );
sInteraction.sPhases[SNPCINT_EXIT].iszSequence = AllocPooledString(szValue);
else if (!Q_strncmp(szName, "exit_activity", 13))
sInteraction.sPhases[SNPCINT_EXIT].iActivity = GetActivityID(szValue);
@ -14857,19 +14865,19 @@ void CAI_BaseNPC::ParseScriptedNPCInteractions( void )
else if (!Q_strncmp(szName, "dont_teleport_at_end", 20))
{
if ( !Q_stricmp( szValue, "me" ) || !Q_stricmp( szValue, "both" ) )
if (!Q_stricmp(szValue, "me") || !Q_stricmp(szValue, "both"))
sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_ME;
else if ( !Q_stricmp( szValue, "them" ) || !Q_stricmp( szValue, "both" ) )
else if (!Q_stricmp(szValue, "them") || !Q_stricmp(szValue, "both"))
sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_THEM;
}
else if (!Q_strncmp(szName, "needs_weapon", 12))
{
if ( !Q_strncmp( szValue, "ME", 2 ) )
if (!Q_strncmp(szValue, "ME", 2))
sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME;
else if ( !Q_strncmp( szValue, "THEM", 4 ) )
else if (!Q_strncmp(szValue, "THEM", 4))
sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM;
else if ( !Q_strncmp( szValue, "BOTH", 4 ) )
else if (!Q_strncmp(szValue, "BOTH", 4))
{
sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME;
sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM;
@ -14879,12 +14887,12 @@ void CAI_BaseNPC::ParseScriptedNPCInteractions( void )
else if (!Q_strncmp(szName, "weapon_mine", 11))
{
sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME;
sInteraction.iszMyWeapon = AllocPooledString( szValue );
sInteraction.iszMyWeapon = AllocPooledString(szValue);
}
else if (!Q_strncmp(szName, "weapon_theirs", 13))
{
sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM;
sInteraction.iszTheirWeapon = AllocPooledString( szValue );
sInteraction.iszTheirWeapon = AllocPooledString(szValue);
}
// Add anything else to our miscellaneous criteria
@ -14908,137 +14916,159 @@ void CAI_BaseNPC::ParseScriptedNPCInteractions( void )
szCriteria += 1;
sInteraction.MiscCriteria = AllocPooledString(szCriteria);
}
// Add it to the list
AddScriptedNPCInteraction(&sInteraction);
iszUsedNames.AddToTail(sInteraction.iszInteractionName);
// Move to next interaction
pkvNode = pkvNode->GetNextKey();
}
}
}
#else
// Trigger method
const char *pszTrigger = pkvNode->GetString( "trigger", NULL );
if ( pszTrigger )
// Do we have a dynamic interactions section?
KeyValues* pkvInteractions = modelKeyValues->FindKey("dynamic_interactions");
if (pkvInteractions)
{
if ( !Q_strncmp( pszTrigger, "auto_in_combat", 14) )
KeyValues* pkvNode = pkvInteractions->GetFirstSubKey();
while (pkvNode)
{
ScriptedNPCInteraction_t sInteraction;
sInteraction.iszInteractionName = AllocPooledString(pkvNode->GetName());
// Trigger method
const char* pszTrigger = pkvNode->GetString("trigger", NULL);
if (pszTrigger)
{
if (!Q_strncmp(pszTrigger, "auto_in_combat", 14))
{
sInteraction.iTriggerMethod = SNPCINT_AUTOMATIC_IN_COMBAT;
}
}
// Loop Break trigger method
pszTrigger = pkvNode->GetString( "loop_break_trigger", NULL );
if ( pszTrigger )
pszTrigger = pkvNode->GetString("loop_break_trigger", NULL);
if (pszTrigger)
{
char szTrigger[256];
Q_strncpy( szTrigger, pszTrigger, sizeof(szTrigger) );
char *pszParam = strtok( szTrigger, " " );
Q_strncpy(szTrigger, pszTrigger, sizeof(szTrigger));
char* pszParam = strtok(szTrigger, " ");
while (pszParam)
{
if ( !Q_strncmp( pszParam, "on_damage", 9) )
if (!Q_strncmp(pszParam, "on_damage", 9))
{
sInteraction.iLoopBreakTriggerMethod |= SNPCINT_LOOPBREAK_ON_DAMAGE;
}
if ( !Q_strncmp( pszParam, "on_flashlight_illum", 19) )
if (!Q_strncmp(pszParam, "on_flashlight_illum", 19))
{
sInteraction.iLoopBreakTriggerMethod |= SNPCINT_LOOPBREAK_ON_FLASHLIGHT_ILLUM;
}
pszParam = strtok(NULL," ");
pszParam = strtok(NULL, " ");
}
}
// Origin
const char *pszOrigin = pkvNode->GetString( "origin_relative", "0 0 0" );
UTIL_StringToVector( sInteraction.vecRelativeOrigin.Base(), pszOrigin );
const char* pszOrigin = pkvNode->GetString("origin_relative", "0 0 0");
UTIL_StringToVector(sInteraction.vecRelativeOrigin.Base(), pszOrigin);
// Angles
const char *pszAngles = pkvNode->GetString( "angles_relative", NULL );
if ( pszAngles )
const char* pszAngles = pkvNode->GetString("angles_relative", NULL);
if (pszAngles)
{
sInteraction.iFlags |= SCNPC_FLAG_TEST_OTHER_ANGLES;
UTIL_StringToVector( sInteraction.angRelativeAngles.Base(), pszAngles );
UTIL_StringToVector(sInteraction.angRelativeAngles.Base(), pszAngles);
}
// Velocity
const char *pszVelocity = pkvNode->GetString( "velocity_relative", NULL );
if ( pszVelocity )
const char* pszVelocity = pkvNode->GetString("velocity_relative", NULL);
if (pszVelocity)
{
sInteraction.iFlags |= SCNPC_FLAG_TEST_OTHER_VELOCITY;
UTIL_StringToVector( sInteraction.vecRelativeVelocity.Base(), pszVelocity );
UTIL_StringToVector(sInteraction.vecRelativeVelocity.Base(), pszVelocity);
}
// Entry Sequence
const char *pszSequence = pkvNode->GetString( "entry_sequence", NULL );
if ( pszSequence )
const char* pszSequence = pkvNode->GetString("entry_sequence", NULL);
if (pszSequence)
{
sInteraction.sPhases[SNPCINT_ENTRY].iszSequence = AllocPooledString( pszSequence );
sInteraction.sPhases[SNPCINT_ENTRY].iszSequence = AllocPooledString(pszSequence);
}
// Entry Activity
const char *pszActivity = pkvNode->GetString( "entry_activity", NULL );
if ( pszActivity )
const char* pszActivity = pkvNode->GetString("entry_activity", NULL);
if (pszActivity)
{
sInteraction.sPhases[SNPCINT_ENTRY].iActivity = GetActivityID( pszActivity );
sInteraction.sPhases[SNPCINT_ENTRY].iActivity = GetActivityID(pszActivity);
}
// Sequence
pszSequence = pkvNode->GetString( "sequence", NULL );
if ( pszSequence )
pszSequence = pkvNode->GetString("sequence", NULL);
if (pszSequence)
{
sInteraction.sPhases[SNPCINT_SEQUENCE].iszSequence = AllocPooledString( pszSequence );
sInteraction.sPhases[SNPCINT_SEQUENCE].iszSequence = AllocPooledString(pszSequence);
}
// Activity
pszActivity = pkvNode->GetString( "activity", NULL );
if ( pszActivity )
pszActivity = pkvNode->GetString("activity", NULL);
if (pszActivity)
{
sInteraction.sPhases[SNPCINT_SEQUENCE].iActivity = GetActivityID( pszActivity );
sInteraction.sPhases[SNPCINT_SEQUENCE].iActivity = GetActivityID(pszActivity);
}
// Exit Sequence
pszSequence = pkvNode->GetString( "exit_sequence", NULL );
if ( pszSequence )
pszSequence = pkvNode->GetString("exit_sequence", NULL);
if (pszSequence)
{
sInteraction.sPhases[SNPCINT_EXIT].iszSequence = AllocPooledString( pszSequence );
sInteraction.sPhases[SNPCINT_EXIT].iszSequence = AllocPooledString(pszSequence);
}
// Exit Activity
pszActivity = pkvNode->GetString( "exit_activity", NULL );
if ( pszActivity )
pszActivity = pkvNode->GetString("exit_activity", NULL);
if (pszActivity)
{
sInteraction.sPhases[SNPCINT_EXIT].iActivity = GetActivityID( pszActivity );
sInteraction.sPhases[SNPCINT_EXIT].iActivity = GetActivityID(pszActivity);
}
// Delay
sInteraction.flDelay = pkvNode->GetFloat( "delay", 10.0 );
sInteraction.flDelay = pkvNode->GetFloat("delay", 10.0);
// Delta
sInteraction.flDistSqr = pkvNode->GetFloat( "origin_max_delta", (DSS_MAX_DIST * DSS_MAX_DIST) );
sInteraction.flDistSqr = pkvNode->GetFloat("origin_max_delta", (DSS_MAX_DIST * DSS_MAX_DIST));
// Loop?
if ( pkvNode->GetFloat( "loop_in_action", 0 ) )
if (pkvNode->GetFloat("loop_in_action", 0))
{
sInteraction.iFlags |= SCNPC_FLAG_LOOP_IN_ACTION;
}
// Fixup position?
const char *pszDontFixup = pkvNode->GetString( "dont_teleport_at_end", NULL );
if ( pszDontFixup )
const char* pszDontFixup = pkvNode->GetString("dont_teleport_at_end", NULL);
if (pszDontFixup)
{
if ( !Q_stricmp( pszDontFixup, "me" ) || !Q_stricmp( pszDontFixup, "both" ) )
if (!Q_stricmp(pszDontFixup, "me") || !Q_stricmp(pszDontFixup, "both"))
{
sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_ME;
}
else if ( !Q_stricmp( pszDontFixup, "them" ) || !Q_stricmp( pszDontFixup, "both" ) )
else if (!Q_stricmp(pszDontFixup, "them") || !Q_stricmp(pszDontFixup, "both"))
{
sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_THEM;
}
}
// Needs a weapon?
const char *pszNeedsWeapon = pkvNode->GetString( "needs_weapon", NULL );
if ( pszNeedsWeapon )
const char* pszNeedsWeapon = pkvNode->GetString("needs_weapon", NULL);
if (pszNeedsWeapon)
{
if ( !Q_strncmp( pszNeedsWeapon, "ME", 2 ) )
if (!Q_strncmp(pszNeedsWeapon, "ME", 2))
{
sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME;
}
else if ( !Q_strncmp( pszNeedsWeapon, "THEM", 4 ) )
else if (!Q_strncmp(pszNeedsWeapon, "THEM", 4))
{
sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM;
}
else if ( !Q_strncmp( pszNeedsWeapon, "BOTH", 4 ) )
else if (!Q_strncmp(pszNeedsWeapon, "BOTH", 4))
{
sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME;
sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM;
@ -15046,27 +15076,28 @@ void CAI_BaseNPC::ParseScriptedNPCInteractions( void )
}
// Specific weapon types
const char *pszWeaponName = pkvNode->GetString( "weapon_mine", NULL );
if ( pszWeaponName )
const char* pszWeaponName = pkvNode->GetString("weapon_mine", NULL);
if (pszWeaponName)
{
sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME;
sInteraction.iszMyWeapon = AllocPooledString( pszWeaponName );
sInteraction.iszMyWeapon = AllocPooledString(pszWeaponName);
}
pszWeaponName = pkvNode->GetString( "weapon_theirs", NULL );
if ( pszWeaponName )
pszWeaponName = pkvNode->GetString("weapon_theirs", NULL);
if (pszWeaponName)
{
sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM;
sInteraction.iszTheirWeapon = AllocPooledString( pszWeaponName );
sInteraction.iszTheirWeapon = AllocPooledString(pszWeaponName);
}
#endif
// Add it to the list
AddScriptedNPCInteraction( &sInteraction );
AddScriptedNPCInteraction(&sInteraction);
// Move to next interaction
pkvNode = pkvNode->GetNextKey();
}
}
#endif // MAPBASE
}
modelKeyValues->deleteThis();