Shadow Walker: Added 'ambush' state to improve squad behavior.

This commit is contained in:
1upD 2018-11-12 22:33:41 -05:00
parent 64841073d9
commit 91556205fb
4 changed files with 152 additions and 33 deletions

View File

@ -17,6 +17,7 @@
#include "engine/IEngineSound.h"
#include "basehlcombatweapon_shared.h"
#include "ai_squadslot.h"
#include "ai_squad.h"
// memdbgon must be the last include file in a .cpp file!!!
@ -48,6 +49,7 @@ BEGIN_DATADESC(CNPC_BaseCustomNPC)
DEFINE_KEYFIELD(m_bCannotOpenDoors, FIELD_BOOLEAN, "CannotOpenDoors"),
DEFINE_KEYFIELD(m_bCanPickupWeapons, FIELD_BOOLEAN, "CanPickupWeapons"),
DEFINE_FIELD(m_iNumSquadmates, FIELD_INTEGER),
DEFINE_FIELD(m_bWanderToggle, FIELD_BOOLEAN),
DEFINE_FIELD(m_flNextSoundTime, FIELD_TIME),
DEFINE_FIELD(m_flNextFoundEnemySoundTime, FIELD_TIME),
@ -245,6 +247,54 @@ int CNPC_BaseCustomNPC::SelectScheduleRetrieveItem()
return SCHED_NONE;
}
//-----------------------------------------------------------------------------
// Purpose: Select ideal state.
// Conditions for custom states are defined here.
//-----------------------------------------------------------------------------
NPC_STATE CNPC_BaseCustomNPC::SelectIdealState(void)
{
switch ((int)this->m_NPCState) {
case NPC_STATE_AMBUSH:
return SelectAmbushIdealState();
case NPC_STATE_SURRENDER:
return SelectSurrenderIdealState();
default:
return BaseClass::SelectIdealState();
}
}
NPC_STATE CNPC_BaseCustomNPC::SelectAmbushIdealState()
{
// AMBUSH goes to ALERT upon death of enemy
if (GetEnemy() == NULL)
{
return NPC_STATE_ALERT;
}
// If I am not in a squad, there is no reason to ambush
if (!m_pSquad) {
return NPC_STATE_COMBAT;
}
// If I am the last in a squad, attack!
if (m_pSquad->NumMembers() == 1) {
return NPC_STATE_COMBAT;
}
if (OccupyStrategySlotRange(SQUAD_SLOT_CHASE_1, SQUAD_SLOT_CHASE_2)) {
return NPC_STATE_COMBAT;
}
// The best ideal state is the current ideal state.
return (NPC_STATE)NPC_STATE_AMBUSH;
}
NPC_STATE CNPC_BaseCustomNPC::SelectSurrenderIdealState()
{
return NPC_STATE_ALERT;
}
//-----------------------------------------------------------------------------
// Purpose: Select a schedule to retrieve better weapons if they are available.
//-----------------------------------------------------------------------------
@ -265,7 +315,7 @@ int CNPC_BaseCustomNPC::SelectScheduleWander()
//-----------------------------------------------------------------------------
int CNPC_BaseCustomNPC::SelectSchedule()
{
switch (m_NPCState)
switch ((int)m_NPCState)
{
case NPC_STATE_IDLE:
AssertMsgOnce(GetEnemy() == NULL, "NPC has enemy but is not in combat state?");
@ -277,7 +327,10 @@ int CNPC_BaseCustomNPC::SelectSchedule()
case NPC_STATE_COMBAT:
return SelectCombatSchedule();
case NPC_STATE_AMBUSH:
return SelectAmbushSchedule();
case NPC_STATE_SURRENDER:
return SelectSurrenderSchedule();
default:
return BaseClass::SelectSchedule();
}
@ -361,6 +414,66 @@ int CNPC_BaseCustomNPC::SelectCombatSchedule()
return BaseClass::SelectSchedule(); // Let Base NPC handle it
}
//-----------------------------------------------------------------------------
// Combat schedule selection
//-----------------------------------------------------------------------------
int CNPC_BaseCustomNPC::SelectAmbushSchedule()
{
// Check enemy death
if (HasCondition(COND_ENEMY_DEAD))
{
// clear the current (dead) enemy and try to find another.
SetEnemy(NULL);
if (ChooseEnemy())
{
SetState(NPC_STATE_COMBAT);
FoundEnemySound();
ClearCondition(COND_ENEMY_DEAD);
return SelectSchedule();
}
SetState(NPC_STATE_ALERT);
return SelectSchedule();
}
CBaseEntity* pEnemy = GetEnemy();
if (pEnemy && EnemyDistance(pEnemy) < 128)
{
SetState(NPC_STATE_COMBAT);
return SelectSchedule();
}
if (pEnemy == NULL || HasCondition(COND_LOST_ENEMY)) {
SetState(NPC_STATE_ALERT);
return SelectSchedule();
}
// If I am the last in a squad, attack!
if (m_iNumSquadmates > m_pSquad->NumMembers())
SetState(SelectAmbushIdealState());
if (HasCondition(COND_LIGHT_DAMAGE)) {
SetState(NPC_STATE_COMBAT);
}
if (HasCondition(COND_SEE_ENEMY) && HasCondition(COND_ENEMY_FACING_ME) && HasCondition(COND_HAVE_ENEMY_LOS)) {
if(GetState() != NPC_STATE_COMBAT)
SetState(SelectAmbushIdealState());
return SCHED_HIDE;
}
m_iNumSquadmates = m_pSquad->NumMembers();
return SCHED_COMBAT_FACE;
}
int CNPC_BaseCustomNPC::SelectSurrenderSchedule()
{
return BaseClass::SelectSchedule();
}
bool CNPC_BaseCustomNPC::HasRangedWeapon()
{
CBaseCombatWeapon *pWeapon = GetActiveWeapon();

View File

@ -29,6 +29,28 @@ enum
LAST_BASE_CUSTOM_SCHED
};
//=========================================================
// states
//=========================================================
enum
{
NPC_STATE_FIRST = NPC_STATE_DEAD,
NPC_STATE_AMBUSH,
NPC_STATE_SURRENDER,
NPC_STATE_LAST_CUSTOM
};
// -----------------------------------------------
// Squad slots
// -----------------------------------------------
enum
{
LAST_SQUADSLOT = 100, // Custom NPCs might share a squad with any NPC, so let's just be safe and skip to a high number
SQUAD_SLOT_CHASE_1,
SQUAD_SLOT_CHASE_2,
LAST_CUSTOM_SQUADSLOT
};
//=========================================================
//=========================================================
typedef CAI_BlendingHost< CAI_BehaviorHost<CAI_BaseNPC> > CAI_CustomNPCBase;
@ -48,10 +70,17 @@ public:
virtual int SelectIdleSchedule();
virtual int SelectAlertSchedule();
virtual int SelectCombatSchedule();
virtual int SelectAmbushSchedule();
virtual int SelectSurrenderSchedule();
virtual float GetSequenceGroundSpeed(CStudioHdr *pStudioHdr, int iSequence);
virtual Activity NPC_TranslateActivity(Activity eNewActivity);
virtual int TranslateSchedule(int scheduleType);
// Custom states
virtual NPC_STATE SelectIdealState(void);
NPC_STATE SelectAmbushIdealState();
NPC_STATE SelectSurrenderIdealState();
// Sounds
virtual void PlaySound(string_t soundname, bool optional);
virtual void DeathSound(const CTakeDamageInfo &info) { PlaySound(m_iszDeathSound, true); }
@ -90,7 +119,7 @@ protected:
bool HasRangedWeapon();
void PrecacheNPCSoundScript(string_t* SoundName, string_t defaultSoundName);
int m_iNumSquadmates;
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.

View File

@ -186,30 +186,6 @@ int CNPC_ShadowWalker::SelectFailSchedule(int failedSchedule, int failedTask, AI
return BaseClass::SelectFailSchedule(failedSchedule, failedTask, taskFailCode);
}
//-----------------------------------------------------------------------------
// Purpose: Select a schedule to execute based on conditions.
// This is the most critical AI method.
//-----------------------------------------------------------------------------
int CNPC_ShadowWalker::SelectSchedule()
{
switch (m_NPCState)
{
case NPC_STATE_IDLE:
AssertMsgOnce(GetEnemy() == NULL, "NPC has enemy but is not in combat state?");
return SelectIdleSchedule();
case NPC_STATE_ALERT:
AssertMsgOnce(GetEnemy() == NULL, "NPC has enemy but is not in combat state?");
return SelectAlertSchedule();
case NPC_STATE_COMBAT:
return SelectCombatSchedule();
default:
return BaseClass::SelectSchedule();
}
}
//-----------------------------------------------------------------------------
// Idle schedule selection
//-----------------------------------------------------------------------------
@ -305,7 +281,6 @@ int CNPC_ShadowWalker::SelectCombatSchedule()
if (ChooseEnemy())
{
FoundEnemySound();
ClearCondition(COND_ENEMY_DEAD);
return SelectSchedule();
}
@ -327,17 +302,20 @@ int CNPC_ShadowWalker::SelectCombatSchedule()
// If in a squad, only one or two shadow walkers can chase the player. This is configurable through Hammer.
bool bCanChase = true;
if (bEnemyCanSeeMe && m_bUseBothSquadSlots) {
bCanChase = OccupyStrategySlotRange(SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2);
if (m_bUseBothSquadSlots) {
bCanChase = OccupyStrategySlotRange(SQUAD_SLOT_CHASE_1, SQUAD_SLOT_CHASE_2);
}
else if (bEnemyCanSeeMe){
bCanChase = OccupyStrategySlot(SQUAD_SLOT_ATTACK1);
else {
bCanChase = OccupyStrategySlot(SQUAD_SLOT_CHASE_1);
}
bCanChase = bCanChase || EnemyDistance(GetEnemy()) < 128;
// If I'm not allowed to chase this enemy of this enemy and he's looking at me, set up an ambush
if (!bCanChase)
{
FearSound();
SetState((NPC_STATE)NPC_STATE_AMBUSH);
return SCHED_HIDE;
}

View File

@ -27,7 +27,6 @@ public:
void Spawn(void);
Class_T Classify(void);
virtual int SelectFailSchedule(int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode);
virtual int SelectSchedule();
virtual int SelectIdleSchedule();
virtual int SelectAlertSchedule();
virtual int SelectCombatSchedule();