mirror of
https://github.com/mapbase-source/source-sdk-2013.git
synced 2025-02-05 02:00:34 +03:00
Merge pull request #270 from Blixibon/mapbase/feature/dynamic-interaction-improvements
NPC dynamic interaction improvements and fixes
This commit is contained in:
commit
4cea5d1123
@ -76,6 +76,23 @@ int CAI_BaseNPC::GetActivityID(const char* actName)
|
|||||||
return m_pActivitySR->GetStringID(actName);
|
return m_pActivitySR->GetStringID(actName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef MAPBASE
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Purpose: Gets an activity ID or registers a new private one if it doesn't exist
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
int CAI_BaseNPC::GetOrRegisterActivity( const char *actName )
|
||||||
|
{
|
||||||
|
int actID = GetActivityID( actName );
|
||||||
|
if (actID == ACT_INVALID)
|
||||||
|
{
|
||||||
|
actID = ActivityList_RegisterPrivateActivity( actName );
|
||||||
|
AddActivityToSR( actName, actID );
|
||||||
|
}
|
||||||
|
|
||||||
|
return actID;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#define ADD_ACTIVITY_TO_SR(activityname) AddActivityToSR(#activityname,activityname)
|
#define ADD_ACTIVITY_TO_SR(activityname) AddActivityToSR(#activityname,activityname)
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -3026,6 +3026,10 @@ void CAI_BaseNPC::PopulatePoseParameters( void )
|
|||||||
m_poseAim_Yaw = LookupPoseParameter( "aim_yaw" );
|
m_poseAim_Yaw = LookupPoseParameter( "aim_yaw" );
|
||||||
m_poseMove_Yaw = LookupPoseParameter( "move_yaw" );
|
m_poseMove_Yaw = LookupPoseParameter( "move_yaw" );
|
||||||
|
|
||||||
|
#ifdef MAPBASE
|
||||||
|
m_poseInteractionRelativeYaw = LookupPoseParameter( "interaction_relative_yaw" );
|
||||||
|
#endif
|
||||||
|
|
||||||
BaseClass::PopulatePoseParameters();
|
BaseClass::PopulatePoseParameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -12450,7 +12454,11 @@ BEGIN_SIMPLE_DATADESC( ScriptedNPCInteraction_t )
|
|||||||
DEFINE_FIELD( bValidOnCurrentEnemy, FIELD_BOOLEAN ),
|
DEFINE_FIELD( bValidOnCurrentEnemy, FIELD_BOOLEAN ),
|
||||||
DEFINE_FIELD( flNextAttemptTime, FIELD_TIME ),
|
DEFINE_FIELD( flNextAttemptTime, FIELD_TIME ),
|
||||||
#ifdef MAPBASE
|
#ifdef MAPBASE
|
||||||
DEFINE_FIELD( MiscCriteria, FIELD_STRING ),//DEFINE_UTLVECTOR( MiscCriteria, FIELD_EMBEDDED ),
|
DEFINE_EMBEDDED_ARRAY( sTheirPhases, SNPCINT_NUM_PHASES ),
|
||||||
|
DEFINE_FIELD( bHasSeparateSequenceNames, FIELD_BOOLEAN ),
|
||||||
|
DEFINE_FIELD( flMaxAngleDiff, FIELD_FLOAT ),
|
||||||
|
DEFINE_FIELD( iszRelatedInteractions, FIELD_STRING ),
|
||||||
|
DEFINE_FIELD( MiscCriteria, FIELD_STRING ),
|
||||||
#endif
|
#endif
|
||||||
END_DATADESC()
|
END_DATADESC()
|
||||||
|
|
||||||
@ -13514,6 +13522,10 @@ bool CAI_BaseNPC::CineCleanup()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear interaction partner, because we're not running a scripted sequence anymore
|
// Clear interaction partner, because we're not running a scripted sequence anymore
|
||||||
|
#ifdef MAPBASE
|
||||||
|
// We need the interaction partner for server ragdoll death cleanup, so don't clear if we're not alive
|
||||||
|
if (IsAlive())
|
||||||
|
#endif
|
||||||
m_hInteractionPartner = NULL;
|
m_hInteractionPartner = NULL;
|
||||||
CleanupForcedInteraction();
|
CleanupForcedInteraction();
|
||||||
}
|
}
|
||||||
@ -14854,32 +14866,66 @@ void CAI_BaseNPC::ParseScriptedNPCInteractions(void)
|
|||||||
else if (!Q_strncmp(szName, "entry_sequence", 14))
|
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))
|
else if (!Q_strncmp(szName, "entry_activity", 14))
|
||||||
sInteraction.sPhases[SNPCINT_ENTRY].iActivity = GetActivityID(szValue);
|
sInteraction.sPhases[SNPCINT_ENTRY].iActivity = GetOrRegisterActivity(szValue);
|
||||||
|
|
||||||
else if (!Q_strncmp(szName, "sequence", 8))
|
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))
|
else if (!Q_strncmp(szName, "activity", 8))
|
||||||
sInteraction.sPhases[SNPCINT_SEQUENCE].iActivity = GetActivityID(szValue);
|
sInteraction.sPhases[SNPCINT_SEQUENCE].iActivity = GetOrRegisterActivity(szValue);
|
||||||
|
|
||||||
else if (!Q_strncmp(szName, "exit_sequence", 13))
|
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))
|
else if (!Q_strncmp(szName, "exit_activity", 13))
|
||||||
sInteraction.sPhases[SNPCINT_EXIT].iActivity = GetActivityID(szValue);
|
sInteraction.sPhases[SNPCINT_EXIT].iActivity = GetOrRegisterActivity(szValue);
|
||||||
|
|
||||||
|
else if (!Q_strncmp(szName, "their_", 6))
|
||||||
|
{
|
||||||
|
const char *szTheirName = szName + 6;
|
||||||
|
sInteraction.bHasSeparateSequenceNames = true;
|
||||||
|
|
||||||
|
if (!Q_strncmp(szTheirName, "entry_sequence", 14))
|
||||||
|
sInteraction.sTheirPhases[SNPCINT_ENTRY].iszSequence = AllocPooledString(szValue);
|
||||||
|
else if (!Q_strncmp(szTheirName, "entry_activity", 14))
|
||||||
|
sInteraction.sTheirPhases[SNPCINT_ENTRY].iActivity = GetOrRegisterActivity(szValue);
|
||||||
|
|
||||||
|
else if (!Q_strncmp(szTheirName, "sequence", 8))
|
||||||
|
sInteraction.sTheirPhases[SNPCINT_SEQUENCE].iszSequence = AllocPooledString(szValue);
|
||||||
|
else if (!Q_strncmp(szTheirName, "activity", 8))
|
||||||
|
sInteraction.sTheirPhases[SNPCINT_SEQUENCE].iActivity = GetOrRegisterActivity(szValue);
|
||||||
|
|
||||||
|
else if (!Q_strncmp(szTheirName, "exit_sequence", 13))
|
||||||
|
sInteraction.sTheirPhases[SNPCINT_EXIT].iszSequence = AllocPooledString(szValue);
|
||||||
|
else if (!Q_strncmp(szTheirName, "exit_activity", 13))
|
||||||
|
sInteraction.sTheirPhases[SNPCINT_EXIT].iActivity = GetOrRegisterActivity(szValue);
|
||||||
|
|
||||||
|
// Add anything else to our miscellaneous criteria
|
||||||
|
else
|
||||||
|
{
|
||||||
|
szCriteria = UTIL_VarArgs("%s,%s:%s", szCriteria, szName, szValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
else if (!Q_strncmp(szName, "delay", 5))
|
else if (!Q_strncmp(szName, "delay", 5))
|
||||||
sInteraction.flDelay = atof(szValue);
|
sInteraction.flDelay = atof(szValue);
|
||||||
else if (!Q_strncmp(szName, "origin_max_delta", 16))
|
else if (!Q_strncmp(szName, "origin_max_delta", 16))
|
||||||
sInteraction.flDistSqr = atof(szValue);
|
sInteraction.flDistSqr = atof(szValue);
|
||||||
|
else if (!Q_strncmp(szName, "angles_max_diff", 15))
|
||||||
|
sInteraction.flMaxAngleDiff = atof(szValue);
|
||||||
|
|
||||||
else if (!Q_strncmp(szName, "loop_in_action", 14) && !FStrEq(szValue, "0"))
|
else if (!Q_strncmp(szName, "loop_in_action", 14) && !FStrEq(szValue, "0"))
|
||||||
sInteraction.iFlags |= SCNPC_FLAG_LOOP_IN_ACTION;
|
sInteraction.iFlags |= SCNPC_FLAG_LOOP_IN_ACTION;
|
||||||
|
|
||||||
else if (!Q_strncmp(szName, "dont_teleport_at_end", 20))
|
else if (!Q_strncmp(szName, "dont_teleport_at_end", 20))
|
||||||
{
|
{
|
||||||
if (!Q_stricmp(szValue, "me") || !Q_stricmp(szValue, "both"))
|
if (!Q_stricmp(szValue, "me"))
|
||||||
sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_ME;
|
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"))
|
||||||
sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_THEM;
|
sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_THEM;
|
||||||
|
else if (!Q_stricmp( szValue, "both" ))
|
||||||
|
{
|
||||||
|
sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_ME;
|
||||||
|
sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_THEM;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (!Q_strncmp(szName, "needs_weapon", 12))
|
else if (!Q_strncmp(szName, "needs_weapon", 12))
|
||||||
@ -14906,6 +14952,11 @@ void CAI_BaseNPC::ParseScriptedNPCInteractions(void)
|
|||||||
sInteraction.iszTheirWeapon = AllocPooledString(szValue);
|
sInteraction.iszTheirWeapon = AllocPooledString(szValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (!Q_strncmp(szName, "related_interactions", 20))
|
||||||
|
{
|
||||||
|
sInteraction.iszRelatedInteractions = AllocPooledString(szValue);
|
||||||
|
}
|
||||||
|
|
||||||
// Add anything else to our miscellaneous criteria
|
// Add anything else to our miscellaneous criteria
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -15137,8 +15188,23 @@ void CAI_BaseNPC::AddScriptedNPCInteraction( ScriptedNPCInteraction_t *pInteract
|
|||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Purpose:
|
// Purpose:
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
const char *CAI_BaseNPC::GetScriptedNPCInteractionSequence( ScriptedNPCInteraction_t *pInteraction, int iPhase )
|
const char *CAI_BaseNPC::GetScriptedNPCInteractionSequence( ScriptedNPCInteraction_t *pInteraction, int iPhase, bool bOtherNPC )
|
||||||
{
|
{
|
||||||
|
#ifdef MAPBASE
|
||||||
|
if (bOtherNPC && pInteraction->bHasSeparateSequenceNames)
|
||||||
|
{
|
||||||
|
// Check unique phases
|
||||||
|
if ( pInteraction->sTheirPhases[iPhase].iActivity != ACT_INVALID )
|
||||||
|
{
|
||||||
|
int iSequence = SelectWeightedSequence( (Activity)pInteraction->sTheirPhases[iPhase].iActivity );
|
||||||
|
return GetSequenceName( iSequence );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( pInteraction->sTheirPhases[iPhase].iszSequence != NULL_STRING )
|
||||||
|
return STRING(pInteraction->sTheirPhases[iPhase].iszSequence);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if ( pInteraction->sPhases[iPhase].iActivity != ACT_INVALID )
|
if ( pInteraction->sPhases[iPhase].iActivity != ACT_INVALID )
|
||||||
{
|
{
|
||||||
int iSequence = SelectWeightedSequence( (Activity)pInteraction->sPhases[iPhase].iActivity );
|
int iSequence = SelectWeightedSequence( (Activity)pInteraction->sPhases[iPhase].iActivity );
|
||||||
@ -15232,6 +15298,37 @@ void CAI_BaseNPC::StartScriptedNPCInteraction( CAI_BaseNPC *pOtherNPC, ScriptedN
|
|||||||
|
|
||||||
// Setup next attempt
|
// Setup next attempt
|
||||||
pInteraction->flNextAttemptTime = gpGlobals->curtime + pInteraction->flDelay + RandomFloat(-2,2);
|
pInteraction->flNextAttemptTime = gpGlobals->curtime + pInteraction->flDelay + RandomFloat(-2,2);
|
||||||
|
#ifdef MAPBASE
|
||||||
|
if (pInteraction->iszRelatedInteractions != NULL_STRING)
|
||||||
|
{
|
||||||
|
// Delay related interactions as well
|
||||||
|
char szRelatedInteractions[256];
|
||||||
|
Q_strncpy( szRelatedInteractions, STRING( pInteraction->iszRelatedInteractions ), sizeof( szRelatedInteractions ) );
|
||||||
|
|
||||||
|
char *pszInteraction = strtok( szRelatedInteractions, "," );
|
||||||
|
while (pszInteraction)
|
||||||
|
{
|
||||||
|
bool bWildCard = Matcher_ContainsWildcard( pszInteraction );
|
||||||
|
|
||||||
|
for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ )
|
||||||
|
{
|
||||||
|
ScriptedNPCInteraction_t *pOtherInteraction = &m_ScriptedInteractions[i];
|
||||||
|
|
||||||
|
if ( Matcher_NamesMatch( pszInteraction, STRING( pOtherInteraction->iszInteractionName ) ) && pOtherInteraction != pInteraction )
|
||||||
|
{
|
||||||
|
if (pOtherInteraction->flNextAttemptTime < pInteraction->flNextAttemptTime)
|
||||||
|
pOtherInteraction->flNextAttemptTime = pInteraction->flNextAttemptTime;
|
||||||
|
|
||||||
|
// Not looking for multiple
|
||||||
|
if (!bWildCard)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pszInteraction = strtok( NULL, "," );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Spawn a scripted sequence for this NPC to play the interaction anim
|
// Spawn a scripted sequence for this NPC to play the interaction anim
|
||||||
CAI_ScriptedSequence *pMySequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" );
|
CAI_ScriptedSequence *pMySequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" );
|
||||||
@ -15263,6 +15360,15 @@ void CAI_BaseNPC::StartScriptedNPCInteraction( CAI_BaseNPC *pOtherNPC, ScriptedN
|
|||||||
CAI_ScriptedSequence *pTheirSequence = NULL;
|
CAI_ScriptedSequence *pTheirSequence = NULL;
|
||||||
if ( pOtherNPC )
|
if ( pOtherNPC )
|
||||||
{
|
{
|
||||||
|
#ifdef MAPBASE
|
||||||
|
if (pInteraction->bHasSeparateSequenceNames)
|
||||||
|
{
|
||||||
|
pszEntrySequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_ENTRY, true );
|
||||||
|
pszSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_SEQUENCE, true );
|
||||||
|
pszExitSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_EXIT, true );
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
pTheirSequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" );
|
pTheirSequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" );
|
||||||
pTheirSequence->KeyValue( "m_iszEntry", pszEntrySequence );
|
pTheirSequence->KeyValue( "m_iszEntry", pszEntrySequence );
|
||||||
pTheirSequence->KeyValue( "m_iszPlay", pszSequence );
|
pTheirSequence->KeyValue( "m_iszPlay", pszSequence );
|
||||||
@ -15286,6 +15392,26 @@ void CAI_BaseNPC::StartScriptedNPCInteraction( CAI_BaseNPC *pOtherNPC, ScriptedN
|
|||||||
|
|
||||||
// Tell their sequence to keep their position relative to me
|
// Tell their sequence to keep their position relative to me
|
||||||
pTheirSequence->SetupInteractionPosition( this, pInteraction->matDesiredLocalToWorld );
|
pTheirSequence->SetupInteractionPosition( this, pInteraction->matDesiredLocalToWorld );
|
||||||
|
|
||||||
|
#ifdef MAPBASE
|
||||||
|
if ( !(pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_ANGLES) )
|
||||||
|
{
|
||||||
|
// Set up interaction yaw pose if it exists
|
||||||
|
float flYaw = AngleDistance( angDesired.y, angOtherAngles.y );
|
||||||
|
|
||||||
|
int nInteractionPose = LookupPoseInteractionRelativeYaw();
|
||||||
|
if (nInteractionPose > -1)
|
||||||
|
{
|
||||||
|
SetPoseParameter( nInteractionPose, flYaw );
|
||||||
|
}
|
||||||
|
|
||||||
|
nInteractionPose = pOtherNPC->LookupPoseInteractionRelativeYaw();
|
||||||
|
if (nInteractionPose > -1)
|
||||||
|
{
|
||||||
|
pOtherNPC->SetPoseParameter( nInteractionPose, flYaw );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spawn both sequences at once
|
// Spawn both sequences at once
|
||||||
@ -15374,7 +15500,7 @@ bool CAI_BaseNPC::CanRunAScriptedNPCInteraction( bool bForced )
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Default AI prevents interactions while melee attacking, but not ranged attacking
|
// Default AI prevents interactions while melee attacking, but not ranged attacking
|
||||||
if ( IsCurSchedule( SCHED_MELEE_ATTACK1 ) || IsCurSchedule( SCHED_MELEE_ATTACK2 ) )
|
if ( ( IsCurSchedule( SCHED_MELEE_ATTACK1 ) || IsCurSchedule( SCHED_MELEE_ATTACK2 ) ) && !CanStartDynamicInteractionDuringMelee() )
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15563,8 +15689,14 @@ void CAI_BaseNPC::CalculateValidEnemyInteractions( void )
|
|||||||
|
|
||||||
if (bSame)
|
if (bSame)
|
||||||
continue;
|
continue;
|
||||||
#endif
|
|
||||||
|
|
||||||
|
// Resolve the activity or sequence, and make sure our enemy has it
|
||||||
|
const char *pszSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_SEQUENCE, true );
|
||||||
|
if ( !pszSequence )
|
||||||
|
continue;
|
||||||
|
if ( pNPC->LookupSequence( pszSequence ) == -1 )
|
||||||
|
continue;
|
||||||
|
#else
|
||||||
// Use sequence? or activity?
|
// Use sequence? or activity?
|
||||||
if ( pInteraction->sPhases[SNPCINT_SEQUENCE].iActivity != ACT_INVALID )
|
if ( pInteraction->sPhases[SNPCINT_SEQUENCE].iActivity != ACT_INVALID )
|
||||||
{
|
{
|
||||||
@ -15580,53 +15712,6 @@ void CAI_BaseNPC::CalculateValidEnemyInteractions( void )
|
|||||||
if ( pNPC->LookupSequence( STRING(pInteraction->sPhases[SNPCINT_SEQUENCE].iszSequence) ) == -1 )
|
if ( pNPC->LookupSequence( STRING(pInteraction->sPhases[SNPCINT_SEQUENCE].iszSequence) ) == -1 )
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef MAPBASE
|
|
||||||
if (pInteraction->MiscCriteria != NULL_STRING)
|
|
||||||
{
|
|
||||||
// Test against response system criteria
|
|
||||||
AI_CriteriaSet set;
|
|
||||||
ModifyOrAppendCriteria( set );
|
|
||||||
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
|
|
||||||
if( pPlayer )
|
|
||||||
pPlayer->ModifyOrAppendPlayerCriteria( set );
|
|
||||||
ReAppendContextCriteria( set );
|
|
||||||
|
|
||||||
DevMsg("Testing %s misc criteria\n", STRING(pInteraction->MiscCriteria));
|
|
||||||
|
|
||||||
int index;
|
|
||||||
const char *criteriavalue;
|
|
||||||
char key[128];
|
|
||||||
char value[128];
|
|
||||||
const char *p = STRING(pInteraction->MiscCriteria);
|
|
||||||
while ( p )
|
|
||||||
{
|
|
||||||
#ifdef NEW_RESPONSE_SYSTEM
|
|
||||||
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL, STRING(pInteraction->MiscCriteria) );
|
|
||||||
#else
|
|
||||||
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL );
|
|
||||||
#endif
|
|
||||||
|
|
||||||
index = set.FindCriterionIndex(key);
|
|
||||||
if (index != -1)
|
|
||||||
{
|
|
||||||
criteriavalue = set.GetValue(index);
|
|
||||||
if (!Matcher_Match(value, criteriavalue))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Test with empty string in case our criteria is != or something
|
|
||||||
criteriavalue = "";
|
|
||||||
if (!Matcher_Match(value, criteriavalue))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
pInteraction->bValidOnCurrentEnemy = true;
|
pInteraction->bValidOnCurrentEnemy = true;
|
||||||
@ -15796,7 +15881,95 @@ bool CAI_BaseNPC::InteractionIsAllowed( CAI_BaseNPC *pOtherNPC, ScriptedNPCInter
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
// m_iDynamicInteractionsAllowed == TRS_FALSE case is already handled in CanRunAScriptedNPCInteraction().
|
// m_iDynamicInteractionsAllowed == TRS_FALSE case is already handled in CanRunAScriptedNPCInteraction().
|
||||||
return !(pInteraction->iFlags & SCNPC_FLAG_MAPBASE_ADDITION && m_iDynamicInteractionsAllowed == TRS_NONE);
|
if (pInteraction->iFlags & SCNPC_FLAG_MAPBASE_ADDITION && m_iDynamicInteractionsAllowed == TRS_NONE)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Test misc. criteria here since some of it may not have been valid on initial calculation, but could be now
|
||||||
|
if (pInteraction->MiscCriteria != NULL_STRING)
|
||||||
|
{
|
||||||
|
// Test against response system criteria
|
||||||
|
AI_CriteriaSet set;
|
||||||
|
ModifyOrAppendCriteria( set );
|
||||||
|
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
|
||||||
|
if (pPlayer)
|
||||||
|
pPlayer->ModifyOrAppendPlayerCriteria( set );
|
||||||
|
|
||||||
|
// Get criteria from target if we want it
|
||||||
|
if ( V_strstr( STRING( pInteraction->MiscCriteria ), "their_" ) )
|
||||||
|
{
|
||||||
|
// Currently, in order to get everything which might be desired, we call the other NPC's ModifyOrAppendCriteria.
|
||||||
|
// We put it in a separate criteria set, then assign a prefix and append it to the main set, similar to how contexts are appended.
|
||||||
|
// This includes a few global criterions which we might not need, so we throw them out before they're merged.
|
||||||
|
// This isn't a very efficient solution, but there are no better options available without rewriting parts of the response criteria system.
|
||||||
|
AI_CriteriaSet theirSet;
|
||||||
|
pOtherNPC->ModifyOrAppendCriteria( theirSet );
|
||||||
|
|
||||||
|
set.EnsureCapacity( (theirSet.GetCount()-2) + set.GetCount() ); // We know we'll be throwing out 2 global criterions
|
||||||
|
|
||||||
|
char sz[ 128 ];
|
||||||
|
for ( int i = 0; i < theirSet.GetCount(); i++ )
|
||||||
|
{
|
||||||
|
const char *name = theirSet.GetName( i );
|
||||||
|
const char *value = theirSet.GetValue( i );
|
||||||
|
|
||||||
|
if (FStrEq( name, "map" ) || FStrEq( name, "episodic" ) || FStrEq( name, "is_console" )
|
||||||
|
|| FStrEq( name, "month" ) || FStrEq( name, "day" )
|
||||||
|
|| FStrEq( name, "is_console" ) || FStrEq( name, "is_pc" )
|
||||||
|
|| V_strnicmp( name, "world", 5 ) == 0)
|
||||||
|
{
|
||||||
|
// Global criterion, ignore
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_snprintf( sz, sizeof( sz ), "their_%s", name );
|
||||||
|
|
||||||
|
if (ai_debug_dyninteractions.GetInt() == 3)
|
||||||
|
Msg( "%i: %s -> %s:%s\n", i, name, sz, value );
|
||||||
|
|
||||||
|
set.AppendCriteria( sz, value );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append this afterwards because it has its own prefix system
|
||||||
|
pOtherNPC->AppendContextToCriteria( set, "their_" );
|
||||||
|
}
|
||||||
|
|
||||||
|
ReAppendContextCriteria( set );
|
||||||
|
|
||||||
|
int index;
|
||||||
|
const char *criteriavalue;
|
||||||
|
char key[128];
|
||||||
|
char value[128];
|
||||||
|
const char *p = STRING( pInteraction->MiscCriteria );
|
||||||
|
while ( p )
|
||||||
|
{
|
||||||
|
#ifdef NEW_RESPONSE_SYSTEM
|
||||||
|
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL, STRING( pInteraction->MiscCriteria ) );
|
||||||
|
#else
|
||||||
|
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL );
|
||||||
|
#endif
|
||||||
|
|
||||||
|
index = set.FindCriterionIndex( key );
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
criteriavalue = set.GetValue( index );
|
||||||
|
if (!Matcher_Match( value, criteriavalue ))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Test with empty string in case our criteria is != or something
|
||||||
|
criteriavalue = "";
|
||||||
|
if (!Matcher_Match( value, criteriavalue ))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -15834,6 +16007,11 @@ bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInte
|
|||||||
{
|
{
|
||||||
Msg(" %s distsqr: %0.2f (%0.2f %0.2f %0.2f), desired: <%0.2f (%0.2f %0.2f %0.2f)\n", GetDebugName(), flDistSqr,
|
Msg(" %s distsqr: %0.2f (%0.2f %0.2f %0.2f), desired: <%0.2f (%0.2f %0.2f %0.2f)\n", GetDebugName(), flDistSqr,
|
||||||
pOtherNPC->GetAbsOrigin().x, pOtherNPC->GetAbsOrigin().y, pOtherNPC->GetAbsOrigin().z, pInteraction->flDistSqr, vecOrigin.x, vecOrigin.y, vecOrigin.z );
|
pOtherNPC->GetAbsOrigin().x, pOtherNPC->GetAbsOrigin().y, pOtherNPC->GetAbsOrigin().z, pInteraction->flDistSqr, vecOrigin.x, vecOrigin.y, vecOrigin.z );
|
||||||
|
#ifdef MAPBASE
|
||||||
|
Vector vecForward, vecRight;
|
||||||
|
GetVectors( &vecForward, &vecRight, NULL );
|
||||||
|
NDebugOverlay::Circle( vecOrigin + Vector(0,0,2), vecForward, vecRight, FastSqrt(pInteraction->flDistSqr), 255, 0, 0, 255, true, 0.1f );
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -15846,6 +16024,11 @@ bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInte
|
|||||||
Msg(" %s is at: %0.2f %0.2f %0.2f\n", GetDebugName(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
|
Msg(" %s is at: %0.2f %0.2f %0.2f\n", GetDebugName(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
|
||||||
Msg(" %s distsqr: %0.2f (%0.2f %0.2f %0.2f), desired: (%0.2f %0.2f %0.2f)\n", GetDebugName(), flDistSqr,
|
Msg(" %s distsqr: %0.2f (%0.2f %0.2f %0.2f), desired: (%0.2f %0.2f %0.2f)\n", GetDebugName(), flDistSqr,
|
||||||
pOtherNPC->GetAbsOrigin().x, pOtherNPC->GetAbsOrigin().y, pOtherNPC->GetAbsOrigin().z, vecOrigin.x, vecOrigin.y, vecOrigin.z );
|
pOtherNPC->GetAbsOrigin().x, pOtherNPC->GetAbsOrigin().y, pOtherNPC->GetAbsOrigin().z, vecOrigin.x, vecOrigin.y, vecOrigin.z );
|
||||||
|
#ifdef MAPBASE
|
||||||
|
Vector vecForward, vecRight;
|
||||||
|
GetVectors( &vecForward, &vecRight, NULL );
|
||||||
|
NDebugOverlay::Circle( vecOrigin + Vector( 0, 0, 2 ), vecForward, vecRight, FastSqrt( pInteraction->flDistSqr ), 255, 0, 0, 255, true, 0.1f );
|
||||||
|
#endif
|
||||||
|
|
||||||
if ( pOtherNPC )
|
if ( pOtherNPC )
|
||||||
{
|
{
|
||||||
@ -15862,14 +16045,28 @@ bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInte
|
|||||||
for ( int ang = 0; ang < 3; ang++ )
|
for ( int ang = 0; ang < 3; ang++ )
|
||||||
{
|
{
|
||||||
float flAngDiff = AngleDiff( angEnemyAngles[ang], angAngles[ang] );
|
float flAngDiff = AngleDiff( angEnemyAngles[ang], angAngles[ang] );
|
||||||
|
#ifdef MAPBASE
|
||||||
|
if ( fabs(flAngDiff) > pInteraction->flMaxAngleDiff )
|
||||||
|
#else
|
||||||
if ( fabs(flAngDiff) > DSS_MAX_ANGLE_DIFF )
|
if ( fabs(flAngDiff) > DSS_MAX_ANGLE_DIFF )
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
bMatches = false;
|
bMatches = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( !bMatches )
|
if ( !bMatches )
|
||||||
|
{
|
||||||
|
#ifdef MAPBASE
|
||||||
|
if ( bDebug )
|
||||||
|
{
|
||||||
|
Msg(" %s angle not matched: (%0.2f %0.2f %0.2f), desired (%0.2f, %0.2f, %0.2f)\n", GetDebugName(),
|
||||||
|
anglemod(angEnemyAngles.x), anglemod(angEnemyAngles.y), anglemod(angEnemyAngles.z), anglemod(angAngles.x), anglemod(angAngles.y), anglemod(angAngles.z) );
|
||||||
|
Msg(" diff: (%0.2f, %0.2f, %0.2f)\n", AngleDiff( angEnemyAngles.x, angAngles.x ), AngleDiff( angEnemyAngles.y, angAngles.y ), AngleDiff( angEnemyAngles.z, angAngles.z ) );
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if ( bDebug )
|
if ( bDebug )
|
||||||
{
|
{
|
||||||
@ -15877,6 +16074,13 @@ bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInte
|
|||||||
anglemod(angEnemyAngles.x), anglemod(angEnemyAngles.y), anglemod(angEnemyAngles.z), anglemod(angAngles.x), anglemod(angAngles.y), anglemod(angAngles.z) );
|
anglemod(angEnemyAngles.x), anglemod(angEnemyAngles.y), anglemod(angEnemyAngles.z), anglemod(angAngles.x), anglemod(angAngles.y), anglemod(angAngles.z) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#ifdef MAPBASE
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If we're not using angles, then use the NPC's current angles
|
||||||
|
angAngles = pOtherNPC->GetAbsAngles();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// TODO: Velocity check, if we're supposed to
|
// TODO: Velocity check, if we're supposed to
|
||||||
if ( pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_VELOCITY )
|
if ( pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_VELOCITY )
|
||||||
@ -15956,6 +16160,7 @@ bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInte
|
|||||||
if ( bDebug )
|
if ( bDebug )
|
||||||
{
|
{
|
||||||
NDebugOverlay::Box( vecPos, GetHullMins(), GetHullMaxs(), 255,0,0, 100, 1.0 );
|
NDebugOverlay::Box( vecPos, GetHullMins(), GetHullMaxs(), 255,0,0, 100, 1.0 );
|
||||||
|
NDebugOverlay::HorzArrow( GetAbsOrigin(), vecPos, 16.0f, 255, 0, 0, 255, true, 1.0f );
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -15963,7 +16168,39 @@ bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInte
|
|||||||
{
|
{
|
||||||
//NDebugOverlay::Box( vecPos, GetHullMins(), GetHullMaxs(), 0,255,0, 100, 1.0 );
|
//NDebugOverlay::Box( vecPos, GetHullMins(), GetHullMaxs(), 0,255,0, 100, 1.0 );
|
||||||
|
|
||||||
NDebugOverlay::Axis( vecPos, angAngles, 20, true, 10.0 );
|
NDebugOverlay::Axis( vecPos, angAngles, 20, true, 1.0 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Instead, make sure we fit into where the sequence movement ends at
|
||||||
|
const char *pszSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_SEQUENCE );
|
||||||
|
int nSeq = LookupSequence( pszSequence );
|
||||||
|
if ( pszSequence && nSeq != -1 )
|
||||||
|
{
|
||||||
|
Vector vecDeltaPos;
|
||||||
|
QAngle angDeltaAngles;
|
||||||
|
GetSequenceMovement( nSeq, 0.0f, 1.0f, vecDeltaPos, angDeltaAngles );
|
||||||
|
if (!vecDeltaPos.IsZero())
|
||||||
|
{
|
||||||
|
QAngle angInteraction = GetAbsAngles();
|
||||||
|
angInteraction[YAW] = m_flInteractionYaw;
|
||||||
|
|
||||||
|
Vector vecPos;
|
||||||
|
VectorRotate( vecDeltaPos, angInteraction, vecPos );
|
||||||
|
vecPos += GetAbsOrigin();
|
||||||
|
|
||||||
|
AI_TraceHull( vecPos, vecPos, GetHullMins(), GetHullMaxs(), MASK_SOLID, &traceFilter, &tr);
|
||||||
|
if ( tr.fraction != 1.0 )
|
||||||
|
{
|
||||||
|
if ( bDebug )
|
||||||
|
{
|
||||||
|
NDebugOverlay::Box( vecPos, GetHullMins(), GetHullMaxs(), 255,0,0, 100, 1.0 );
|
||||||
|
NDebugOverlay::HorzArrow( GetAbsOrigin(), vecPos, 16.0f, 255, 0, 0, 255, true, 1.0f );
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -15987,6 +16224,25 @@ bool CAI_BaseNPC::HasInteractionCantDie( void )
|
|||||||
return ( m_bCannotDieDuringInteraction && IsRunningDynamicInteraction() );
|
return ( m_bCannotDieDuringInteraction && IsRunningDynamicInteraction() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Purpose: Return true if this NPC has valid interactions on the current enemy.
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
bool CAI_BaseNPC::HasValidInteractionsOnCurrentEnemy( void )
|
||||||
|
{
|
||||||
|
if ( !GetEnemy() || !GetEnemy()->IsNPC() )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ )
|
||||||
|
{
|
||||||
|
ScriptedNPCInteraction_t *pInteraction = &m_ScriptedInteractions[i];
|
||||||
|
|
||||||
|
if ( pInteraction->bValidOnCurrentEnemy )
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Purpose:
|
// Purpose:
|
||||||
// Input : &inputdata -
|
// Input : &inputdata -
|
||||||
|
@ -425,6 +425,9 @@ struct ScriptedNPCInteraction_t
|
|||||||
iszTheirWeapon = NULL_STRING;
|
iszTheirWeapon = NULL_STRING;
|
||||||
#ifdef MAPBASE
|
#ifdef MAPBASE
|
||||||
vecRelativeEndPos = vec3_origin;
|
vecRelativeEndPos = vec3_origin;
|
||||||
|
bHasSeparateSequenceNames = false;
|
||||||
|
flMaxAngleDiff = DSS_MAX_ANGLE_DIFF;
|
||||||
|
iszRelatedInteractions = NULL_STRING;
|
||||||
MiscCriteria = NULL_STRING;
|
MiscCriteria = NULL_STRING;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -432,6 +435,10 @@ struct ScriptedNPCInteraction_t
|
|||||||
{
|
{
|
||||||
sPhases[i].iszSequence = NULL_STRING;
|
sPhases[i].iszSequence = NULL_STRING;
|
||||||
sPhases[i].iActivity = ACT_INVALID;
|
sPhases[i].iActivity = ACT_INVALID;
|
||||||
|
#ifdef MAPBASE
|
||||||
|
sTheirPhases[i].iszSequence = NULL_STRING;
|
||||||
|
sTheirPhases[i].iActivity = ACT_INVALID;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,10 +466,14 @@ struct ScriptedNPCInteraction_t
|
|||||||
float flNextAttemptTime;
|
float flNextAttemptTime;
|
||||||
|
|
||||||
#ifdef MAPBASE
|
#ifdef MAPBASE
|
||||||
// Unrecognized keyvalues are tested against response criteria later.
|
ScriptedNPCInteraction_Phases_t sTheirPhases[SNPCINT_NUM_PHASES]; // The animations played by the target NPC, if they are different
|
||||||
// This was originally a CUtlVector that carries response contexts, but I couldn't get it working due to some CUtlVector-struct shenanigans.
|
bool bHasSeparateSequenceNames;
|
||||||
// It works when we use a single string_t that's split and read each time the code runs, but feel free to improve on this.
|
|
||||||
string_t MiscCriteria; // CUtlVector<ResponseContext_t>
|
float flMaxAngleDiff;
|
||||||
|
string_t iszRelatedInteractions; // These interactions will be delayed as well when this interaction is used.
|
||||||
|
|
||||||
|
// Unrecognized keyvalues which are tested against response criteria later.
|
||||||
|
string_t MiscCriteria;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
DECLARE_SIMPLE_DATADESC();
|
DECLARE_SIMPLE_DATADESC();
|
||||||
@ -838,6 +849,9 @@ protected: // pose parameters
|
|||||||
int m_poseAim_Pitch;
|
int m_poseAim_Pitch;
|
||||||
int m_poseAim_Yaw;
|
int m_poseAim_Yaw;
|
||||||
int m_poseMove_Yaw;
|
int m_poseMove_Yaw;
|
||||||
|
#ifdef MAPBASE
|
||||||
|
int m_poseInteractionRelativeYaw;
|
||||||
|
#endif
|
||||||
virtual void PopulatePoseParameters( void );
|
virtual void PopulatePoseParameters( void );
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -845,6 +859,10 @@ public:
|
|||||||
|
|
||||||
// Return the stored pose parameter for "move_yaw"
|
// Return the stored pose parameter for "move_yaw"
|
||||||
inline int LookupPoseMoveYaw() { return m_poseMove_Yaw; }
|
inline int LookupPoseMoveYaw() { return m_poseMove_Yaw; }
|
||||||
|
|
||||||
|
#ifdef MAPBASE
|
||||||
|
inline int LookupPoseInteractionRelativeYaw() { return m_poseInteractionRelativeYaw; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------
|
//-----------------------------------------------------
|
||||||
@ -1304,10 +1322,14 @@ private:
|
|||||||
public:
|
public:
|
||||||
float GetInteractionYaw( void ) const { return m_flInteractionYaw; }
|
float GetInteractionYaw( void ) const { return m_flInteractionYaw; }
|
||||||
|
|
||||||
|
bool IsRunningDynamicInteraction( void ) { return (m_iInteractionState != NPCINT_NOT_RUNNING && (m_hCine != NULL)); }
|
||||||
|
bool IsActiveDynamicInteraction( void ) { return (m_iInteractionState == NPCINT_RUNNING_ACTIVE && (m_hCine != NULL)); }
|
||||||
|
CAI_BaseNPC *GetInteractionPartner( void );
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void ParseScriptedNPCInteractions( void );
|
void ParseScriptedNPCInteractions( void );
|
||||||
void AddScriptedNPCInteraction( ScriptedNPCInteraction_t *pInteraction );
|
void AddScriptedNPCInteraction( ScriptedNPCInteraction_t *pInteraction );
|
||||||
const char *GetScriptedNPCInteractionSequence( ScriptedNPCInteraction_t *pInteraction, int iPhase );
|
const char *GetScriptedNPCInteractionSequence( ScriptedNPCInteraction_t *pInteraction, int iPhase, bool bOtherNPC = false );
|
||||||
void StartRunningInteraction( CAI_BaseNPC *pOtherNPC, bool bActive );
|
void StartRunningInteraction( CAI_BaseNPC *pOtherNPC, bool bActive );
|
||||||
void StartScriptedNPCInteraction( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction, Vector vecOtherOrigin, QAngle angOtherAngles );
|
void StartScriptedNPCInteraction( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction, Vector vecOtherOrigin, QAngle angOtherAngles );
|
||||||
void CheckForScriptedNPCInteractions( void );
|
void CheckForScriptedNPCInteractions( void );
|
||||||
@ -1320,17 +1342,16 @@ protected:
|
|||||||
#endif
|
#endif
|
||||||
bool InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction, Vector &vecOrigin, QAngle &angAngles );
|
bool InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction, Vector &vecOrigin, QAngle &angAngles );
|
||||||
virtual bool CanRunAScriptedNPCInteraction( bool bForced = false );
|
virtual bool CanRunAScriptedNPCInteraction( bool bForced = false );
|
||||||
bool IsRunningDynamicInteraction( void ) { return (m_iInteractionState != NPCINT_NOT_RUNNING && (m_hCine != NULL)); }
|
|
||||||
bool IsActiveDynamicInteraction( void ) { return (m_iInteractionState == NPCINT_RUNNING_ACTIVE && (m_hCine != NULL)); }
|
|
||||||
ScriptedNPCInteraction_t *GetRunningDynamicInteraction( void ) { return &(m_ScriptedInteractions[m_iInteractionPlaying]); }
|
ScriptedNPCInteraction_t *GetRunningDynamicInteraction( void ) { return &(m_ScriptedInteractions[m_iInteractionPlaying]); }
|
||||||
void SetInteractionCantDie( bool bCantDie ) { m_bCannotDieDuringInteraction = bCantDie; }
|
void SetInteractionCantDie( bool bCantDie ) { m_bCannotDieDuringInteraction = bCantDie; }
|
||||||
bool HasInteractionCantDie( void );
|
bool HasInteractionCantDie( void );
|
||||||
|
bool HasValidInteractionsOnCurrentEnemy( void );
|
||||||
|
virtual bool CanStartDynamicInteractionDuringMelee() { return false; }
|
||||||
|
|
||||||
void InputForceInteractionWithNPC( inputdata_t &inputdata );
|
void InputForceInteractionWithNPC( inputdata_t &inputdata );
|
||||||
void StartForcedInteraction( CAI_BaseNPC *pNPC, int iInteraction );
|
void StartForcedInteraction( CAI_BaseNPC *pNPC, int iInteraction );
|
||||||
void CleanupForcedInteraction( void );
|
void CleanupForcedInteraction( void );
|
||||||
void CalculateForcedInteractionPosition( void );
|
void CalculateForcedInteractionPosition( void );
|
||||||
CAI_BaseNPC *GetInteractionPartner( void );
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Forced interactions
|
// Forced interactions
|
||||||
@ -2228,6 +2249,9 @@ public:
|
|||||||
static const char* GetActivityName (int actID);
|
static const char* GetActivityName (int actID);
|
||||||
|
|
||||||
static void AddActivityToSR(const char *actName, int conID);
|
static void AddActivityToSR(const char *actName, int conID);
|
||||||
|
#ifdef MAPBASE
|
||||||
|
static int GetOrRegisterActivity( const char *actName );
|
||||||
|
#endif
|
||||||
|
|
||||||
static void AddEventToSR(const char *eventName, int conID);
|
static void AddEventToSR(const char *eventName, int conID);
|
||||||
static const char* GetEventName (int actID);
|
static const char* GetEventName (int actID);
|
||||||
|
@ -1610,6 +1610,12 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask )
|
|||||||
// as this should only run with the NPC "receiving" the interaction
|
// as this should only run with the NPC "receiving" the interaction
|
||||||
ScriptedNPCInteraction_t *pInteraction = m_hForcedInteractionPartner->GetRunningDynamicInteraction();
|
ScriptedNPCInteraction_t *pInteraction = m_hForcedInteractionPartner->GetRunningDynamicInteraction();
|
||||||
|
|
||||||
|
if ( !(pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_ANGLES) )
|
||||||
|
{
|
||||||
|
TaskComplete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Get our target's origin
|
// Get our target's origin
|
||||||
Vector vecTarget = m_hForcedInteractionPartner->GetAbsOrigin();
|
Vector vecTarget = m_hForcedInteractionPartner->GetAbsOrigin();
|
||||||
|
|
||||||
@ -1617,7 +1623,7 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask )
|
|||||||
float angInteractionAngle = pInteraction->angRelativeAngles.y;
|
float angInteractionAngle = pInteraction->angRelativeAngles.y;
|
||||||
angInteractionAngle += 180.0f;
|
angInteractionAngle += 180.0f;
|
||||||
|
|
||||||
GetMotor()->SetIdealYaw( CalcIdealYaw( vecTarget ) + angInteractionAngle );
|
GetMotor()->SetIdealYaw( AngleNormalize( CalcIdealYaw( vecTarget ) + angInteractionAngle ) );
|
||||||
|
|
||||||
if (FacingIdeal())
|
if (FacingIdeal())
|
||||||
TaskComplete();
|
TaskComplete();
|
||||||
@ -4113,6 +4119,15 @@ void CAI_BaseNPC::RunTask( const Task_t *pTask )
|
|||||||
m_hCine->SynchronizeSequence( this );
|
m_hCine->SynchronizeSequence( this );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef MAPBASE
|
||||||
|
if ( IsRunningDynamicInteraction() && m_poseInteractionRelativeYaw > -1 )
|
||||||
|
{
|
||||||
|
// Animations in progress require pose parameters to be set every frame, so keep setting the interaction relative yaw pose.
|
||||||
|
// The random value is added to help it pass server transmit checks.
|
||||||
|
SetPoseParameter( m_poseInteractionRelativeYaw, GetPoseParameter( m_poseInteractionRelativeYaw ) + RandomFloat( -0.1f, 0.1f ) );
|
||||||
|
}
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1745,7 +1745,11 @@ void CNPC_BaseZombie::HandleAnimEvent( animevent_t *pEvent )
|
|||||||
|
|
||||||
dmgInfo.SetDamagePosition( vecHeadCrabPosition );
|
dmgInfo.SetDamagePosition( vecHeadCrabPosition );
|
||||||
|
|
||||||
|
#ifdef MAPBASE
|
||||||
|
ReleaseHeadcrab( vecHeadCrabPosition, vVelocity *iSpeed, true, false, true );
|
||||||
|
#else
|
||||||
ReleaseHeadcrab( EyePosition(), vVelocity * iSpeed, true, false, true );
|
ReleaseHeadcrab( EyePosition(), vVelocity * iSpeed, true, false, true );
|
||||||
|
#endif
|
||||||
|
|
||||||
GuessDamageForce( &dmgInfo, vVelocity, vecHeadCrabPosition, 0.5f );
|
GuessDamageForce( &dmgInfo, vVelocity, vecHeadCrabPosition, 0.5f );
|
||||||
TakeDamage( dmgInfo );
|
TakeDamage( dmgInfo );
|
||||||
@ -2435,6 +2439,20 @@ void CNPC_BaseZombie::RemoveHead( void )
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------
|
||||||
|
//---------------------------------------------------------
|
||||||
|
void CNPC_BaseZombie::SetModel( const char *szModelName )
|
||||||
|
{
|
||||||
|
#ifdef MAPBASE
|
||||||
|
// Zombies setting the same model again is a problem when they should maintain their current sequence (e.g. during dynamic interactions)
|
||||||
|
if ( IsRunningDynamicInteraction() && GetModelIndex() != 0 && FStrEq( szModelName, STRING(GetModelName()) ) )
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
BaseClass::SetModel( szModelName );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool CNPC_BaseZombie::ShouldPlayFootstepMoan( void )
|
bool CNPC_BaseZombie::ShouldPlayFootstepMoan( void )
|
||||||
{
|
{
|
||||||
if( random->RandomInt( 1, zombie_stepfreq.GetInt() * s_iAngryZombies ) == 1 )
|
if( random->RandomInt( 1, zombie_stepfreq.GetInt() * s_iAngryZombies ) == 1 )
|
||||||
|
@ -186,6 +186,7 @@ public:
|
|||||||
// Headcrab releasing/breaking apart
|
// Headcrab releasing/breaking apart
|
||||||
void RemoveHead( void );
|
void RemoveHead( void );
|
||||||
virtual void SetZombieModel( void ) { };
|
virtual void SetZombieModel( void ) { };
|
||||||
|
virtual void SetModel( const char *szModelName );
|
||||||
virtual void BecomeTorso( const Vector &vecTorsoForce, const Vector &vecLegsForce );
|
virtual void BecomeTorso( const Vector &vecTorsoForce, const Vector &vecLegsForce );
|
||||||
virtual bool CanBecomeLiveTorso() { return false; }
|
virtual bool CanBecomeLiveTorso() { return false; }
|
||||||
virtual bool HeadcrabFits( CBaseAnimating *pCrab );
|
virtual bool HeadcrabFits( CBaseAnimating *pCrab );
|
||||||
|
@ -47,6 +47,8 @@ ConVar npc_combine_protected_run( "npc_combine_protected_run", "0", FCVAR_NONE,
|
|||||||
ConVar npc_combine_altfire_not_allies_only( "npc_combine_altfire_not_allies_only", "1", FCVAR_NONE, "Mapbase: Elites are normally only allowed to fire their alt-fire attack at the player and the player's allies; This allows elites to alt-fire at other enemies too." );
|
ConVar npc_combine_altfire_not_allies_only( "npc_combine_altfire_not_allies_only", "1", FCVAR_NONE, "Mapbase: Elites are normally only allowed to fire their alt-fire attack at the player and the player's allies; This allows elites to alt-fire at other enemies too." );
|
||||||
|
|
||||||
ConVar npc_combine_new_cover_behavior( "npc_combine_new_cover_behavior", "1", FCVAR_NONE, "Mapbase: Toggles small patches for parts of npc_combine AI related to soldiers failing to take cover. These patches are minimal and only change cases where npc_combine would otherwise look at an enemy without shooting or run up to the player to melee attack when they don't have to. Consult the Mapbase wiki for more information." );
|
ConVar npc_combine_new_cover_behavior( "npc_combine_new_cover_behavior", "1", FCVAR_NONE, "Mapbase: Toggles small patches for parts of npc_combine AI related to soldiers failing to take cover. These patches are minimal and only change cases where npc_combine would otherwise look at an enemy without shooting or run up to the player to melee attack when they don't have to. Consult the Mapbase wiki for more information." );
|
||||||
|
|
||||||
|
ConVar npc_combine_fixed_shootpos( "npc_combine_fixed_shootpos", "0", FCVAR_NONE, "Mapbase: Toggles fixed Combine soldier shoot position." );
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define COMBINE_SKIN_DEFAULT 0
|
#define COMBINE_SKIN_DEFAULT 0
|
||||||
@ -2959,6 +2961,28 @@ Vector CNPC_Combine::Weapon_ShootPosition( )
|
|||||||
// FIXME: rename this "estimated" since it's not based on animation
|
// FIXME: rename this "estimated" since it's not based on animation
|
||||||
// FIXME: the orientation won't be correct when testing from arbitary positions for arbitary angles
|
// FIXME: the orientation won't be correct when testing from arbitary positions for arbitary angles
|
||||||
|
|
||||||
|
#ifdef MAPBASE
|
||||||
|
// HACKHACK: This weapon shoot position code does not work properly when in close range, causing the aim
|
||||||
|
// to drift to the left as the enemy gets closer to it.
|
||||||
|
// This problem is usually bearable for regular combat, but it causes dynamic interaction yaw to be offset
|
||||||
|
// as well, preventing most from ever being triggered.
|
||||||
|
// Ideally, this should be fixed from the root cause, but due to the sensitivity of such a change, this is
|
||||||
|
// currently being tied to a cvar which is off by default.
|
||||||
|
//
|
||||||
|
// If the cvar is disabled but the soldier has valid interactions on its current enemy, then a separate hack
|
||||||
|
// will still attempt to correct the drift as the enemy gets closer.
|
||||||
|
if ( npc_combine_fixed_shootpos.GetBool() )
|
||||||
|
{
|
||||||
|
right *= 0.0f;
|
||||||
|
}
|
||||||
|
else if ( HasValidInteractionsOnCurrentEnemy() )
|
||||||
|
{
|
||||||
|
float flDistSqr = GetEnemy()->WorldSpaceCenter().DistToSqr( WorldSpaceCenter() );
|
||||||
|
if (flDistSqr < Square( 128.0f ))
|
||||||
|
right *= (flDistSqr / Square( 128.0f ));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if ( bStanding )
|
if ( bStanding )
|
||||||
{
|
{
|
||||||
if( HasShotgun() )
|
if( HasShotgun() )
|
||||||
|
@ -1557,6 +1557,16 @@ CBaseEntity *CreateServerRagdoll( CBaseAnimating *pAnimating, int forceBone, con
|
|||||||
pRagdoll->CollisionProp()->SetCollisionBounds( mins, maxs );
|
pRagdoll->CollisionProp()->SetCollisionBounds( mins, maxs );
|
||||||
|
|
||||||
#ifdef MAPBASE
|
#ifdef MAPBASE
|
||||||
|
// If this was a NPC running a dynamic interaction, disable collisions with the interaction partner
|
||||||
|
if (pAnimating->IsNPC() /*&& pAnimating->MyNPCPointer()->IsRunningDynamicInteraction()*/)
|
||||||
|
{
|
||||||
|
CAI_BaseNPC *pNPC = pAnimating->MyNPCPointer();
|
||||||
|
if (pNPC->GetInteractionPartner() && pNPC->GetInteractionPartner()->VPhysicsGetObject())
|
||||||
|
{
|
||||||
|
PhysDisableEntityCollisions( pRagdoll, pNPC->GetInteractionPartner() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
variant_t variant;
|
variant_t variant;
|
||||||
variant.SetEntity(pRagdoll);
|
variant.SetEntity(pRagdoll);
|
||||||
pAnimating->FireNamedOutput("OnServerRagdoll", variant, pRagdoll, pAnimating);
|
pAnimating->FireNamedOutput("OnServerRagdoll", variant, pRagdoll, pAnimating);
|
||||||
|
@ -1396,11 +1396,31 @@ void CAI_ScriptedSequence::ModifyScriptedAutoMovement( Vector *vecNewPos )
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VMatrix matInteractionPosition = m_matInteractionPosition;
|
||||||
|
|
||||||
|
#ifdef MAPBASE
|
||||||
|
// Account for our own sequence movement
|
||||||
|
pAnimating = m_hTargetEnt->GetBaseAnimating();
|
||||||
|
if (pAnimating)
|
||||||
|
{
|
||||||
|
Vector vecDeltaPos;
|
||||||
|
QAngle angDeltaAngles;
|
||||||
|
|
||||||
|
pAnimating->GetSequenceMovement( pAnimating->GetSequence(), 0.0f, pAnimating->GetCycle(), vecDeltaPos, angDeltaAngles );
|
||||||
|
if (!vecDeltaPos.IsZero())
|
||||||
|
{
|
||||||
|
VMatrix matLocalMovement;
|
||||||
|
matLocalMovement.SetupMatrixOrgAngles( vecDeltaPos, angDeltaAngles );
|
||||||
|
MatrixMultiply( m_matInteractionPosition, matLocalMovement, matInteractionPosition );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// We've been asked to maintain a specific position relative to the other NPC
|
// We've been asked to maintain a specific position relative to the other NPC
|
||||||
// we're interacting with. Lerp towards the relative position.
|
// we're interacting with. Lerp towards the relative position.
|
||||||
VMatrix matMeToWorld, matLocalToWorld;
|
VMatrix matMeToWorld, matLocalToWorld;
|
||||||
matMeToWorld.SetupMatrixOrgAngles( vecRelativeOrigin, angRelativeAngles );
|
matMeToWorld.SetupMatrixOrgAngles( vecRelativeOrigin, angRelativeAngles );
|
||||||
MatrixMultiply( matMeToWorld, m_matInteractionPosition, matLocalToWorld );
|
MatrixMultiply( matMeToWorld, matInteractionPosition, matLocalToWorld );
|
||||||
|
|
||||||
// Get the desired NPC position in worldspace
|
// Get the desired NPC position in worldspace
|
||||||
Vector vecOrigin;
|
Vector vecOrigin;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user