Shadow Walker: Behavior bugfixes including melee attacks being interruptable, perpetual screaming, and incorrect squad behavior

This commit is contained in:
Derek Dik 2018-10-22 16:54:06 -04:00
parent 1007a6434a
commit 029944b409
2 changed files with 185 additions and 35 deletions

View File

@ -6,7 +6,7 @@
//=============================================================================
@include "base.fgd"
@include "halflife2"
@include "halflife2.fgd"
//-------------------------------------------------------------------------
//
@ -45,6 +45,22 @@
0 : "Use One Squad Slot"
1 : "Use Both Squad Slots"
]
CannotOpenDoors(choices) : "Can Open Doors?" : 0 : "Is this NPC able to open doors? You can change this after spawning with EnableOpenDoors and DisableOpenDoors, but it doesn't always seem to work well with pathfinding." =
[
0 : "Can Open Doors"
1 : "Cannot Open Doors"
]
CanPickupWeapons(choices) : "Can Pick Up Guns?" : 0 : "Is this NPC able to pick up guns? You can change this after spawning with EnablePickupWeapons and DisablePickupWeapons." =
[
0 : "Cannot Pick Up Guns"
1 : "Can Pick Up Guns"
]
input SetSpeedModifier(float) : "Set a float value to multiple distance traveled by."
input EnableOpenDoors(void) : "Allow this NPC to open doors. (Warning: Doesn't always seem to update pathfinding / AI)"
input DisableOpenDoors(void) : "Prevent this NPC from opening doors."
input EnablePickupWeapons(void) : "Allow this NPC to pick up any weapon off of the ground."
input DisablePickupWeapons(void) : "Prevent this NPC from picking up weapons."
]
@NPCClass base(npc_manhack) studio("models/skeleton/skeleton_torso3.mdl") = npc_lost_soul : "Lost Soul"

View File

@ -29,6 +29,15 @@
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//=========================================================
// schedules
//=========================================================
enum
{
SCHED_MELEE_ATTACK_NOINTERRUPT,
SCHED_HIDE
};
//=========================================================
//=========================================================
class CNPC_ShadowWalker : public CAI_BaseNPC
@ -41,12 +50,15 @@ public:
Class_T Classify( void );
virtual int SelectFailSchedule(int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode);
virtual int SelectScheduleRetrieveItem();
virtual int SelectScheduleWander();
virtual int SelectSchedule();
virtual int SelectIdleSchedule();
virtual int SelectAlertSchedule();
virtual int SelectCombatSchedule();
virtual bool CanPickkUpWeapons() { return true; }
virtual bool CanPickkUpWeapons() { return m_bCanPickupWeapons; }
Activity NPC_TranslateActivity(Activity eNewActivity);
virtual int TranslateSchedule(int scheduleType);
// Sounds
virtual void PlaySound(string_t soundname, bool optional);
@ -56,7 +68,7 @@ public:
virtual void PainSound(const CTakeDamageInfo &info) { PlaySound(m_iszPainSound, true); };
virtual void FearSound(void) { PlaySound(m_iszFearSound, false); };
virtual void LostEnemySound(void) { PlaySound(m_iszLostEnemySound, false); };
virtual void FoundEnemySound(void) { PlaySound(m_iszFoundEnemySound, false); };
virtual void FoundEnemySound(void);
void Activate();
void FixupWeapon();
@ -65,6 +77,8 @@ public:
virtual void InputSetSpeed(inputdata_t &inputdata);
virtual void InputEnableOpenDoors(inputdata_t &inputdata);
virtual void InputDisableOpenDoors(inputdata_t &inputdata);
virtual void InputEnablePickupWeapons(inputdata_t &inputdata);
virtual void InputDisablePickupWeapons(inputdata_t &inputdata);
DECLARE_DATADESC();
@ -85,10 +99,12 @@ private:
void PrecacheNPCSoundScript(string_t* SoundName, string_t defaultSoundName);
bool m_bUseBothSquadSlots; // If true use two squad slots, if false use one squad slot
bool m_bCannotOpenDoors; // If true, this NPC cannot open doors. The condition is reversed because originally it could.
bool m_bWanderToggle; // Boolean to toggle wandering / standing every think cycle
float m_flNextSoundTime; // Next time at which this NPC is allowed to play an NPC sound
bool m_bUseBothSquadSlots; // If true use two squad slots, if false use one squad slot
bool m_bCannotOpenDoors; // If true, this NPC cannot open doors. The condition is reversed because originally it could.
bool m_bCanPickupWeapons; // If true, this NPC is able to pick up weapons off of the ground just like npc_citizen.
bool m_bWanderToggle; // Boolean to toggle wandering / standing every think cycle
float m_flNextSoundTime; // Next time at which this NPC is allowed to play an NPC sound
float m_flNextFoundEnemySoundTime; // Next time at which this NPC is allowed to play an NPC sound
};
@ -111,15 +127,67 @@ BEGIN_DATADESC(CNPC_ShadowWalker)
DEFINE_KEYFIELD(m_iszFoundEnemySound, FIELD_SOUNDNAME, "FoundEnemySound"),
DEFINE_KEYFIELD(m_bUseBothSquadSlots, FIELD_BOOLEAN, "UseBothSquadSlots"),
DEFINE_KEYFIELD(m_bCannotOpenDoors, FIELD_BOOLEAN, "CannotOpenDoors"),
DEFINE_KEYFIELD(m_bCanPickupWeapons, FIELD_BOOLEAN, "CanPickupWeapons"),
DEFINE_FIELD(m_bWanderToggle, FIELD_BOOLEAN),
DEFINE_FIELD(m_flNextSoundTime, FIELD_TIME),
DEFINE_FIELD(m_flNextFoundEnemySoundTime, FIELD_TIME),
DEFINE_INPUTFUNC(FIELD_FLOAT, "SetSpeed", InputSetSpeed),
DEFINE_INPUTFUNC(FIELD_VOID, "EnableOpenDoors", InputEnableOpenDoors),
DEFINE_INPUTFUNC(FIELD_VOID, "DisableOpenDoors", InputDisableOpenDoors)
DEFINE_INPUTFUNC(FIELD_VOID, "DisableOpenDoors", InputDisableOpenDoors),
DEFINE_INPUTFUNC(FIELD_VOID, "EnablePickupWeapons", InputEnablePickupWeapons),
DEFINE_INPUTFUNC(FIELD_VOID, "DisablePickupWeapons", InputDisablePickupWeapons)
END_DATADESC()
//=========================================================
// > Melee_Attack_NoInterrupt
//=========================================================
AI_DEFINE_SCHEDULE
(
SCHED_MELEE_ATTACK_NOINTERRUPT,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_FACE_ENEMY 0"
" TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
" TASK_MELEE_ATTACK1 0"
""
" Interrupts"
" COND_ENEMY_DEAD"
" COND_ENEMY_OCCLUDED"
);
//=========================================================
// SCHED_HIDE
//=========================================================
AI_DEFINE_SCHEDULE
(
SCHED_HIDE,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBAT_FACE"
" TASK_STOP_MOVING 0"
" TASK_FIND_COVER_FROM_ENEMY 0"
" TASK_RUN_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
" TASK_REMEMBER MEMORY:INCOVER"
" TASK_FACE_ENEMY 0"
""
" Interrupts"
" COND_HEAR_DANGER"
" COND_NEW_ENEMY"
" COND_ENEMY_DEAD"
);
//---------------------------------------------------------
// Constants
//---------------------------------------------------------
const float MIN_TIME_NEXT_SOUND = 0.5f;
const float MAX_TIME_NEXT_SOUND = 1.0f;
const float MIN_TIME_NEXT_FOUNDENEMY_SOUND = 2.0f;
const float MAX_TIME_NEXT_FOUNDENEMY_SOUND = 5.0f;
//-----------------------------------------------------------------------------
// Purpose: Initialize the custom schedules
// Input :
@ -203,6 +271,7 @@ void CNPC_ShadowWalker::Spawn( void )
m_flFieldOfView = 0.5;
m_flNextSoundTime = gpGlobals->curtime;
m_flNextFoundEnemySoundTime = gpGlobals->curtime;
m_NPCState = NPC_STATE_NONE;
CapabilitiesClear();
@ -252,9 +321,6 @@ void CNPC_ShadowWalker::FixupWeapon()
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_ShadowWalker::Activate()
{
BaseClass::Activate();
@ -310,6 +376,20 @@ int CNPC_ShadowWalker::SelectScheduleRetrieveItem()
return SCHED_NONE;
}
//-----------------------------------------------------------------------------
// Purpose: Select a schedule to retrieve better weapons if they are available.
//-----------------------------------------------------------------------------
int CNPC_ShadowWalker::SelectScheduleWander()
{
m_bWanderToggle = !m_bWanderToggle;
if (m_bWanderToggle) {
return SCHED_IDLE_WANDER;
}
else {
return SCHED_NONE;
}
}
//-----------------------------------------------------------------------------
// Purpose: Select a schedule to execute based on conditions.
// This is the most critical AI method.
@ -353,7 +433,7 @@ int CNPC_ShadowWalker::SelectIdleSchedule()
return SCHED_INVESTIGATE_SOUND;
}
if (CanPickkUpWeapons() && HasCondition(COND_BETTER_WEAPON_AVAILABLE)) {
if (CanPickkUpWeapons()) {
nSched = SelectScheduleRetrieveItem();
if (nSched != SCHED_NONE)
return nSched;
@ -361,13 +441,9 @@ int CNPC_ShadowWalker::SelectIdleSchedule()
// no valid route! Wander instead
if (GetNavigator()->GetGoalType() == GOALTYPE_NONE) {
m_bWanderToggle = !m_bWanderToggle;
if (m_bWanderToggle) {
return SCHED_IDLE_WANDER;
}
else {
nSched = SelectScheduleWander();
if (nSched == SCHED_NONE)
return SCHED_IDLE_STAND;
}
}
// valid route. Get moving
@ -399,7 +475,7 @@ int CNPC_ShadowWalker::SelectAlertSchedule()
return SCHED_INVESTIGATE_SOUND;
}
if (CanPickkUpWeapons() && HasCondition(COND_BETTER_WEAPON_AVAILABLE)) {
if (CanPickkUpWeapons()) {
nSched = SelectScheduleRetrieveItem();
if (nSched != SCHED_NONE)
return nSched;
@ -407,13 +483,9 @@ int CNPC_ShadowWalker::SelectAlertSchedule()
// no valid route! Wander instead
if (GetNavigator()->GetGoalType() == GOALTYPE_NONE) {
m_bWanderToggle = !m_bWanderToggle;
if (m_bWanderToggle) {
return SCHED_IDLE_WANDER;
}
else {
return SCHED_ALERT_STAND;
}
nSched = SelectScheduleWander();
if (nSched == SCHED_NONE)
return SCHED_IDLE_STAND;
}
// valid route. Get moving
@ -439,6 +511,7 @@ int CNPC_ShadowWalker::SelectCombatSchedule()
if (ChooseEnemy())
{
FoundEnemySound();
ClearCondition(COND_ENEMY_DEAD);
return SelectSchedule();
}
@ -447,21 +520,32 @@ int CNPC_ShadowWalker::SelectCombatSchedule()
return SelectSchedule();
}
// Can any enemies see me?
bool bEnemyCanSeeMe = HasCondition(COND_SEE_ENEMY) && HasCondition(COND_ENEMY_FACING_ME) && HasCondition(COND_HAVE_ENEMY_LOS);
// If I'm scared of this enemy and he's looking at me, run away
if ((IRelationType(GetEnemy()) == D_FR) && bEnemyCanSeeMe)
{
FearSound();
return SCHED_RUN_FROM_ENEMY;
}
// If in a squad, only one or two shadow walkers can chase the player. This is configurable through Hammer.
bool bCanChase = false;
if (m_bUseBothSquadSlots) {
bool bCanChase = true;
if (bEnemyCanSeeMe && m_bUseBothSquadSlots) {
bCanChase = OccupyStrategySlotRange(SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2);
}
else {
else if (bEnemyCanSeeMe){
bCanChase = OccupyStrategySlot(SQUAD_SLOT_ATTACK1);
}
// If I'm scared of this enemy and he's looking at me, run away
if ((IRelationType(GetEnemy()) == D_FR || !bCanChase) && (HasCondition(COND_SEE_ENEMY) && HasCondition(COND_ENEMY_FACING_ME) && HasCondition(COND_HAVE_ENEMY_LOS)))
// If I'm not allowed to chase this enemy of this enemy and he's looking at me, set up an ambush
if (!bCanChase)
{
// TODO: Check if silent
FearSound();
return SCHED_RUN_FROM_ENEMY;
FearSound();
return SCHED_HIDE;
}
// Reloading conditions are necessary just in case for some reason somebody gives the Shadow Walker a gun
@ -533,12 +617,30 @@ bool CNPC_ShadowWalker::HasRangedWeapon()
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Override base class schedules
//-----------------------------------------------------------------------------
int CNPC_ShadowWalker::TranslateSchedule(int scheduleType)
{
switch (scheduleType)
{
case SCHED_MELEE_ATTACK1:
return SCHED_MELEE_ATTACK_NOINTERRUPT;
case SCHED_IDLE_WANDER: // We want idle wandering to be interruptible - patrol walk is a better schedule
return SCHED_PATROL_WALK;
}
return BaseClass::TranslateSchedule(scheduleType);
}
//-----------------------------------------------------------------------------
// Purpose: Override base class activiites
//-----------------------------------------------------------------------------
Activity CNPC_ShadowWalker::NPC_TranslateActivity(Activity activity)
{
switch (activity) {
case ACT_IDLE_MELEE:
return ACT_IDLE; // If the walker has a melee weapon but is in an idle state, don't raise the weapon
case ACT_RUN_AIM_SHOTGUN:
return ACT_RUN_AIM_RIFLE;
case ACT_WALK_AIM_SHOTGUN:
@ -552,14 +654,30 @@ Activity CNPC_ShadowWalker::NPC_TranslateActivity(Activity activity)
}
}
//-----------------------------------------------------------------------------
// Purpose: Play sound when an enemy is spotted. This sound has a separate
// timer from other sounds to prevent looping if the NPC gets caught
// in a 'found enemy' condition.
//-----------------------------------------------------------------------------
void CNPC_ShadowWalker::FoundEnemySound(void)
{
if (gpGlobals->curtime > m_flNextFoundEnemySoundTime)
{
m_flNextFoundEnemySoundTime = gpGlobals->curtime + random->RandomFloat(MIN_TIME_NEXT_FOUNDENEMY_SOUND, MAX_TIME_NEXT_FOUNDENEMY_SOUND);
PlaySound(m_iszFoundEnemySound, true);
}
}
//-----------------------------------------------------------------------------
// Purpose: Play NPC soundscript
//-----------------------------------------------------------------------------
void CNPC_ShadowWalker::PlaySound(string_t soundname, bool required /*= false */)
{
// TODO: Check if silent
if (required || gpGlobals->curtime > m_flNextSoundTime)
{
m_flNextSoundTime = gpGlobals->curtime + random->RandomFloat(0.5, 1.0);
m_flNextSoundTime = gpGlobals->curtime + random->RandomFloat(MIN_TIME_NEXT_SOUND, MAX_TIME_NEXT_SOUND);
//CPASAttenuationFilter filter2(this, STRING(soundname));
EmitSound(STRING(soundname));
}
@ -608,6 +726,22 @@ void CNPC_ShadowWalker::InputDisableOpenDoors(inputdata_t &inputdata)
}
}
//-----------------------------------------------------------------------------
// Purpose: Hammer input to enable weapon pickup behavior
//-----------------------------------------------------------------------------
void CNPC_ShadowWalker::InputEnablePickupWeapons(inputdata_t &inputdata)
{
m_bCanPickupWeapons = true;
}
//-----------------------------------------------------------------------------
// Purpose: Hammer input to enable weapon pickup behavior
//-----------------------------------------------------------------------------
void CNPC_ShadowWalker::InputDisablePickupWeapons(inputdata_t &inputdata)
{
m_bCanPickupWeapons = false;
}
//-----------------------------------------------------------------------------
// Purpose:
//