Added "fake sequence gestures" for NPCs, which play certain activities as gestures instead when the current animation needs to be preserved

This commit is contained in:
Blixibon 2021-10-27 23:55:55 -05:00
parent 26c05ee685
commit dfa7e6c0c2
2 changed files with 185 additions and 1 deletions

View File

@ -169,6 +169,8 @@ extern ConVar ai_vehicle_avoidance;
extern ISoundEmitterSystemBase *soundemitterbase;
ConVar ai_dynint_always_enabled( "ai_dynint_always_enabled", "0", FCVAR_NONE, "Makes the \"Don't Care\" setting equivalent to \"Yes\"." );
ConVar ai_debug_fake_sequence_gestures_always_play( "ai_debug_fake_sequence_gestures_always_play", "0", FCVAR_NONE, "Always plays fake sequence gestures." );
#endif
#ifndef _RETAIL
@ -313,6 +315,8 @@ ScriptHook_t CAI_BaseNPC::g_Hook_QuerySeeEntity;
ScriptHook_t CAI_BaseNPC::g_Hook_TranslateActivity;
ScriptHook_t CAI_BaseNPC::g_Hook_TranslateSchedule;
ScriptHook_t CAI_BaseNPC::g_Hook_GetActualShootPosition;
ScriptHook_t CAI_BaseNPC::g_Hook_OverrideMove;
ScriptHook_t CAI_BaseNPC::g_Hook_ShouldPlayFakeSequenceGesture;
#endif
//
@ -7048,6 +7052,23 @@ void CAI_BaseNPC::SetIdealActivity( Activity NewActivity )
// Perform translation in case we need to change sequences within a single activity,
// such as between a standing idle and a crouching idle.
ResolveActivityToSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity);
#ifdef MAPBASE
// Check if we need a gesture to imitate this sequence
if ( ShouldPlayFakeSequenceGesture( m_IdealActivity, m_IdealTranslatedActivity ) )
{
Activity nGesture = SelectFakeSequenceGesture( m_IdealActivity, m_IdealTranslatedActivity );
if (nGesture != -1)
{
PlayFakeSequenceGesture( nGesture, m_IdealActivity, m_IdealTranslatedActivity );
}
}
else if (GetFakeSequenceGesture() != -1)
{
// Reset the current gesture sequence if there is one
ResetFakeSequenceGesture();
}
#endif
}
@ -7096,6 +7117,14 @@ void CAI_BaseNPC::AdvanceToIdealActivity(void)
//DevMsg("%s: Unable to get from sequence %s to %s!\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(m_nIdealSequence));
SetActivity(m_IdealActivity);
}
#ifdef MAPBASE
// If there was a gesture imitating a sequence, get rid of it
if ( GetFakeSequenceGesture() != -1 )
{
ResetFakeSequenceGesture();
}
#endif
}
@ -7153,6 +7182,12 @@ void CAI_BaseNPC::MaintainActivity(void)
}
// Else a transition sequence is in progress, do nothing.
}
#ifdef MAPBASE
else if (GetFakeSequenceGesture() != -1)
{
// Don't advance even if the sequence gesture is finished, as advancing would just play the original activity afterwards
}
#endif
// Else get a specific sequence for the activity and try to transition to that.
else
{
@ -7171,11 +7206,104 @@ void CAI_BaseNPC::MaintainActivity(void)
}
#ifdef MAPBASE
bool CAI_BaseNPC::ShouldPlayFakeSequenceGesture( Activity nActivity, Activity nTranslatedActivity )
{
// Don't do anything if we're resetting our activity
if (GetActivity() == ACT_RESET)
return false;
// No need to do this while we're moving
if (IsCurTaskContinuousMove())
return false;
if (ai_debug_fake_sequence_gestures_always_play.GetBool())
return true;
#ifdef MAPBASE_VSCRIPT
if (m_ScriptScope.IsInitialized() && g_Hook_ShouldPlayFakeSequenceGesture.CanRunInScope(m_ScriptScope))
{
// activity, translatedActivity
ScriptVariant_t functionReturn;
ScriptVariant_t args[] = { GetActivityName( nActivity ), GetActivityName( nTranslatedActivity ) };
if (g_Hook_ShouldPlayFakeSequenceGesture.Call( m_ScriptScope, &functionReturn, args ))
{
if (functionReturn.m_type == FIELD_BOOLEAN)
return functionReturn.m_bool;
}
}
#endif
if (GetHintNode() && GetHintNode()->HintActivityName() != NULL_STRING)
{
switch (GetHintNode()->HintType())
{
// Cover nodes with custom activities should allow NPCs to do things like reload while in cover.
case HINT_TACTICAL_COVER_LOW:
case HINT_TACTICAL_COVER_MED:
case HINT_TACTICAL_COVER_CUSTOM:
if (HasMemory( bits_MEMORY_INCOVER ))
{
// Don't attack while using a custom animation in cover
if (nActivity != ACT_RANGE_ATTACK1 && nActivity != ACT_RANGE_ATTACK1_LOW)
return true;
}
break;
}
}
return false;
}
Activity CAI_BaseNPC::SelectFakeSequenceGesture( Activity nActivity, Activity nTranslatedActivity )
{
return GetGestureVersionOfActivity( nTranslatedActivity );
}
inline void CAI_BaseNPC::PlayFakeSequenceGesture( Activity nActivity, Activity nSequence, Activity nTranslatedSequence )
{
RestartGesture( nActivity, true, true );
m_FakeSequenceGestureLayer = FindGestureLayer( nActivity );
switch ( nSequence )
{
case ACT_RANGE_ATTACK1:
//case ACT_RANGE_ATTACK2:
{
OnRangeAttack1();
// FIXME: this seems a bit wacked
Weapon_SetActivity( Weapon_TranslateActivity( nSequence ), 0 );
} break;
}
}
inline int CAI_BaseNPC::GetFakeSequenceGesture()
{
return m_FakeSequenceGestureLayer;
}
void CAI_BaseNPC::ResetFakeSequenceGesture()
{
SetLayerCycle( m_FakeSequenceGestureLayer, 1.0f );
m_FakeSequenceGestureLayer = -1;
}
#endif
//-----------------------------------------------------------------------------
// Purpose: Returns true if our ideal activity has finished playing.
//-----------------------------------------------------------------------------
bool CAI_BaseNPC::IsActivityFinished( void )
{
#ifdef MAPBASE
if (GetFakeSequenceGesture() != -1)
{
Msg( "Checking if fake sequence gesture is finished\n" );
return IsLayerFinished( GetFakeSequenceGesture() );
}
#endif
return (IsSequenceFinished() && (GetSequence() == m_nIdealSequence));
}
@ -11972,6 +12100,7 @@ BEGIN_DATADESC( CAI_BaseNPC )
DEFINE_KEYFIELD( m_FriendlyFireOverride, FIELD_INTEGER, "FriendlyFireOverride" ),
DEFINE_KEYFIELD( m_flSpeedModifier, FIELD_FLOAT, "BaseSpeedModifier" ),
DEFINE_FIELD( m_FakeSequenceGestureLayer, FIELD_INTEGER ),
#endif
// Satisfy classcheck
@ -12129,6 +12258,11 @@ BEGIN_ENT_SCRIPTDESC( CAI_BaseNPC, CBaseCombatCharacter, "The base class all NPC
DEFINE_SCRIPTFUNC_NAMED( ScriptSetActivityID, "SetActivityID", "Set the NPC's current activity ID." )
DEFINE_SCRIPTFUNC( ResetActivity, "Reset the NPC's current activity." )
DEFINE_SCRIPTFUNC_NAMED( VScriptGetGestureVersionOfActivity, "GetGestureVersionOfActivity", "Get the gesture activity counterpart of the specified sequence activity, if one exists." )
DEFINE_SCRIPTFUNC_NAMED( VScriptGetGestureVersionOfActivityID, "GetGestureVersionOfActivityID", "Get the gesture activity ID counterpart of the specified sequence activity ID, if one exists." )
DEFINE_SCRIPTFUNC_NAMED( VScriptGetSequenceVersionOfGesture, "GetSequenceVersionOfGesture", "Get the sequence activity counterpart of the specified gesture activity, if one exists." )
DEFINE_SCRIPTFUNC_NAMED( VScriptGetSequenceVersionOfGestureID, "GetSequenceVersionOfGestureID", "Get the sequence activity ID counterpart of the specified gesture activity ID, if one exists." )
DEFINE_SCRIPTFUNC_NAMED( VScriptGetSchedule, "GetSchedule", "Get the NPC's current schedule." )
DEFINE_SCRIPTFUNC_NAMED( VScriptGetScheduleID, "GetScheduleID", "Get the NPC's current schedule ID." )
DEFINE_SCRIPTFUNC_NAMED( VScriptSetSchedule, "SetSchedule", "Set the NPC's current schedule." )
@ -12175,10 +12309,17 @@ BEGIN_ENT_SCRIPTDESC( CAI_BaseNPC, CBaseCombatCharacter, "The base class all NPC
DEFINE_SCRIPTHOOK_PARAM( "schedule", FIELD_CSTRING )
DEFINE_SCRIPTHOOK_PARAM( "schedule_id", FIELD_INTEGER )
END_SCRIPTHOOK()
BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_GetActualShootPosition, "GetActualShootPosition", FIELD_VOID, "Called when the NPC is getting their actual shoot position, using the default shoot position as the parameter. (NOTE: NPCs which override this themselves might not always use this hook!)" )
BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_GetActualShootPosition, "GetActualShootPosition", FIELD_VECTOR, "Called when the NPC is getting their actual shoot position, using the default shoot position as the parameter. (NOTE: NPCs which override this themselves might not always use this hook!)" )
DEFINE_SCRIPTHOOK_PARAM( "shootOrigin", FIELD_VECTOR )
DEFINE_SCRIPTHOOK_PARAM( "target", FIELD_HSCRIPT )
END_SCRIPTHOOK()
BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_OverrideMove, "OverrideMove", FIELD_VOID, "Called when the NPC runs movement code, allowing the NPC's movement to be overridden by some other method. (NOTE: NPCs which override this themselves might not always use this hook!)" )
DEFINE_SCRIPTHOOK_PARAM( "interval", FIELD_FLOAT )
END_SCRIPTHOOK()
BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_ShouldPlayFakeSequenceGesture, "ShouldPlayFakeSequenceGesture", FIELD_BOOLEAN, "Called when an activity is set on a NPC. Returning true will make the NPC convert the activity into a gesture (if a gesture is available) and continue their current activity instead." )
DEFINE_SCRIPTHOOK_PARAM( "activity", FIELD_CSTRING )
DEFINE_SCRIPTHOOK_PARAM( "translatedActivity", FIELD_CSTRING )
END_SCRIPTHOOK()
END_SCRIPTDESC();
#endif
@ -12829,6 +12970,8 @@ CAI_BaseNPC::CAI_BaseNPC(void)
#ifdef MAPBASE
m_iDynamicInteractionsAllowed = TRS_NONE;
m_flSpeedModifier = 1.0f;
m_FakeSequenceGestureLayer = -1;
#endif
}

View File

@ -1021,6 +1021,25 @@ public:
void SetActivityAndSequence(Activity NewActivity, int iSequence, Activity translatedActivity, Activity weaponActivity);
#ifdef MAPBASE
//-----------------------------------------------------
// Returns the gesture variant of an activity (i.e. "ACT_GESTURE_RANGE_ATTACK1")
static Activity GetGestureVersionOfActivity( Activity inActivity );
// Returns the sequence variant of a gesture activity
static Activity GetSequenceVersionOfGesture( Activity inActivity );
//-----------------------------------------------------
virtual bool ShouldPlayFakeSequenceGesture( Activity nActivity, Activity nTranslatedActivity );
virtual Activity SelectFakeSequenceGesture( Activity nActivity, Activity nTranslatedActivity );
void PlayFakeSequenceGesture( Activity nActivity, Activity nSequence, Activity nTranslatedSequence );
int GetFakeSequenceGesture();
void ResetFakeSequenceGesture();
#endif
private:
void AdvanceToIdealActivity(void);
@ -1034,6 +1053,10 @@ private:
Activity m_IdealTranslatedActivity; // Desired actual translated animation state
Activity m_IdealWeaponActivity; // Desired weapon animation state
#ifdef MAPBASE
int m_FakeSequenceGestureLayer; // The gesture layer impersonating a sequence (-1 if invalid)
#endif
CNetworkVar(int, m_iDeathPose );
CNetworkVar(int, m_iDeathFrame );
@ -1218,6 +1241,8 @@ public:
#endif
#ifdef MAPBASE_VSCRIPT
private:
// VScript stuff uses "VScript" instead of just "Script" to avoid
// confusion with NPC_STATE_SCRIPT or StartScripting
HSCRIPT VScriptGetEnemy();
@ -1244,6 +1269,11 @@ public:
int ScriptTranslateActivity( const char *szActivity ) { return TranslateActivity( (Activity)GetActivityID( szActivity ) ); }
int ScriptTranslateActivityID( int iActivity ) { return TranslateActivity( (Activity)iActivity ); }
const char* VScriptGetGestureVersionOfActivity( const char *pszActivity ) { return GetActivityName( GetGestureVersionOfActivity( (Activity)GetActivityID( pszActivity ) ) ); }
int VScriptGetGestureVersionOfActivityID( int iActivity ) { return GetGestureVersionOfActivity( (Activity)iActivity ); }
const char* VScriptGetSequenceVersionOfGesture( const char *pszActivity ) { return GetActivityName( GetSequenceVersionOfGesture( (Activity)GetActivityID( pszActivity ) ) ); }
int VScriptGetSequenceVersionOfGestureID( int iActivity ) { return GetSequenceVersionOfGesture( (Activity)iActivity ); }
const char* VScriptGetSchedule();
int VScriptGetScheduleID();
void VScriptSetSchedule( const char *szSchedule );
@ -2271,6 +2301,16 @@ private:
static CAI_GlobalScheduleNamespace gm_SchedulingSymbols;
static CAI_ClassScheduleIdSpace gm_ClassScheduleIdSpace;
#ifdef MAPBASE
typedef struct
{
Activity sequence;
Activity gesture;
} actlink_t;
static actlink_t gm_ActivityGestureLinks[];
#endif
public:
//----------------------------------------------------
// Debugging tools
@ -2322,6 +2362,7 @@ public:
static ScriptHook_t g_Hook_TranslateSchedule;
static ScriptHook_t g_Hook_GetActualShootPosition;
static ScriptHook_t g_Hook_OverrideMove;
static ScriptHook_t g_Hook_ShouldPlayFakeSequenceGesture;
#endif
private: