mirror of
https://github.com/nillerusr/source-engine.git
synced 2026-08-09 10:19:38 +00:00
1
This commit is contained in:
@@ -0,0 +1,776 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Father Grigori, a benevolent monk who is the last remaining human
|
||||
// in Ravenholm. He keeps to the rooftops and uses a big ole elephant
|
||||
// gun to send his zombified former friends to a peaceful death.
|
||||
//
|
||||
//=============================================================================//
|
||||
|
||||
#include "cbase.h"
|
||||
#include "ai_baseactor.h"
|
||||
#include "ai_hull.h"
|
||||
#include "ammodef.h"
|
||||
#include "gamerules.h"
|
||||
#include "IEffects.h"
|
||||
#include "engine/IEngineSound.h"
|
||||
#include "ai_behavior.h"
|
||||
#include "ai_behavior_assault.h"
|
||||
#include "ai_behavior_lead.h"
|
||||
#include "npcevent.h"
|
||||
#include "ai_playerally.h"
|
||||
#include "ai_senses.h"
|
||||
#include "soundent.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
ConVar monk_headshot_freq( "monk_headshot_freq", "2" );
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Activities.
|
||||
//-----------------------------------------------------------------------------
|
||||
int ACT_MONK_GUN_IDLE;
|
||||
|
||||
class CNPC_Monk : public CAI_PlayerAlly
|
||||
{
|
||||
DECLARE_CLASS( CNPC_Monk, CAI_PlayerAlly );
|
||||
|
||||
public:
|
||||
|
||||
CNPC_Monk() {}
|
||||
void Spawn();
|
||||
void Precache();
|
||||
|
||||
bool CreateBehaviors();
|
||||
int GetSoundInterests();
|
||||
void BuildScheduleTestBits( void );
|
||||
Class_T Classify( void );
|
||||
|
||||
bool ShouldBackAway();
|
||||
|
||||
bool IsValidEnemy( CBaseEntity *pEnemy );
|
||||
|
||||
int TranslateSchedule( int scheduleType );
|
||||
int SelectSchedule ();
|
||||
|
||||
void HandleAnimEvent( animevent_t *pEvent );
|
||||
Activity NPC_TranslateActivity( Activity eNewActivity );
|
||||
|
||||
void PainSound( const CTakeDamageInfo &info );
|
||||
void DeathSound( const CTakeDamageInfo &info );
|
||||
|
||||
WeaponProficiency_t CalcWeaponProficiency( CBaseCombatWeapon *pWeapon );
|
||||
Vector GetActualShootPosition( const Vector &shootOrigin );
|
||||
Vector GetActualShootTrajectory( const Vector &shootOrigin );
|
||||
|
||||
void PrescheduleThink();
|
||||
|
||||
void StartTask( const Task_t *pTask );
|
||||
void RunTask( const Task_t *pTask );
|
||||
|
||||
void GatherConditions();
|
||||
|
||||
bool PassesDamageFilter( const CTakeDamageInfo &info );
|
||||
void OnKilledNPC( CBaseCombatCharacter *pKilled );
|
||||
|
||||
bool IsJumpLegal( const Vector &startPos, const Vector &apex, const Vector &endPos ) const;
|
||||
int SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode );
|
||||
|
||||
DECLARE_DATADESC();
|
||||
|
||||
private:
|
||||
//-----------------------------------------------------
|
||||
// Conditions, Schedules, Tasks
|
||||
//-----------------------------------------------------
|
||||
enum
|
||||
{
|
||||
SCHED_MONK_RANGE_ATTACK1 = BaseClass::NEXT_SCHEDULE,
|
||||
SCHED_MONK_BACK_AWAY_FROM_ENEMY,
|
||||
SCHED_MONK_BACK_AWAY_AND_RELOAD,
|
||||
SCHED_MONK_NORMAL_RELOAD,
|
||||
};
|
||||
|
||||
/*enum
|
||||
{
|
||||
//TASK_MONK_FIRST_TASK = BaseClass::NEXT_TASK,
|
||||
};*/
|
||||
|
||||
DEFINE_CUSTOM_AI;
|
||||
|
||||
// Inputs
|
||||
void InputPerfectAccuracyOn( inputdata_t &inputdata );
|
||||
void InputPerfectAccuracyOff( inputdata_t &inputdata );
|
||||
|
||||
CAI_AssaultBehavior m_AssaultBehavior;
|
||||
CAI_LeadBehavior m_LeadBehavior;
|
||||
int m_iNumZombies;
|
||||
int m_iDangerousZombies;
|
||||
bool m_bPerfectAccuracy;
|
||||
bool m_bMournedPlayer;
|
||||
|
||||
};
|
||||
|
||||
BEGIN_DATADESC( CNPC_Monk )
|
||||
// m_AssaultBehavior
|
||||
// m_LeadBehavior
|
||||
DEFINE_FIELD( m_iNumZombies, FIELD_INTEGER ),
|
||||
DEFINE_FIELD( m_iDangerousZombies, FIELD_INTEGER ),
|
||||
DEFINE_FIELD( m_bPerfectAccuracy, FIELD_BOOLEAN ),
|
||||
DEFINE_FIELD( m_bMournedPlayer, FIELD_BOOLEAN ),
|
||||
|
||||
// Inputs
|
||||
DEFINE_INPUTFUNC( FIELD_VOID, "PerfectAccuracyOn", InputPerfectAccuracyOn ),
|
||||
DEFINE_INPUTFUNC( FIELD_VOID, "PerfectAccuracyOff", InputPerfectAccuracyOff ),
|
||||
|
||||
END_DATADESC()
|
||||
|
||||
LINK_ENTITY_TO_CLASS( npc_monk, CNPC_Monk );
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNPC_Monk::CreateBehaviors()
|
||||
{
|
||||
AddBehavior( &m_LeadBehavior );
|
||||
AddBehavior( &m_AssaultBehavior );
|
||||
|
||||
return BaseClass::CreateBehaviors();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
int CNPC_Monk::GetSoundInterests()
|
||||
{
|
||||
return SOUND_WORLD |
|
||||
SOUND_COMBAT |
|
||||
SOUND_PLAYER |
|
||||
SOUND_DANGER;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNPC_Monk::BuildScheduleTestBits( void )
|
||||
{
|
||||
// FIXME: we need a way to make scenes non-interruptible
|
||||
#if 0
|
||||
if ( IsCurSchedule( SCHED_RANGE_ATTACK1 ) || IsCurSchedule( SCHED_SCENE_GENERIC ) )
|
||||
{
|
||||
ClearCustomInterruptCondition( COND_LIGHT_DAMAGE );
|
||||
ClearCustomInterruptCondition( COND_HEAVY_DAMAGE );
|
||||
ClearCustomInterruptCondition( COND_NEW_ENEMY );
|
||||
ClearCustomInterruptCondition( COND_HEAR_DANGER );
|
||||
}
|
||||
#endif
|
||||
|
||||
// Don't interrupt while shooting the gun
|
||||
const Task_t* pTask = GetTask();
|
||||
if ( pTask && (pTask->iTask == TASK_RANGE_ATTACK1) )
|
||||
{
|
||||
ClearCustomInterruptCondition( COND_HEAVY_DAMAGE );
|
||||
ClearCustomInterruptCondition( COND_ENEMY_OCCLUDED );
|
||||
ClearCustomInterruptCondition( COND_HEAR_DANGER );
|
||||
ClearCustomInterruptCondition( COND_WEAPON_BLOCKED_BY_FRIEND );
|
||||
ClearCustomInterruptCondition( COND_WEAPON_SIGHT_OCCLUDED );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
Class_T CNPC_Monk::Classify( void )
|
||||
{
|
||||
return CLASS_PLAYER_ALLY_VITAL;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
Activity CNPC_Monk::NPC_TranslateActivity( Activity eNewActivity )
|
||||
{
|
||||
eNewActivity = BaseClass::NPC_TranslateActivity( eNewActivity );
|
||||
|
||||
if ( (m_NPCState == NPC_STATE_COMBAT || m_NPCState == NPC_STATE_ALERT) )
|
||||
{
|
||||
bool bGunUp = false;
|
||||
|
||||
bGunUp = (gpGlobals->curtime - m_flLastAttackTime < 4);
|
||||
bGunUp = bGunUp || (GetEnemy() && !HasCondition( COND_TOO_FAR_TO_ATTACK ));
|
||||
|
||||
if (bGunUp)
|
||||
{
|
||||
if ( eNewActivity == ACT_IDLE )
|
||||
{
|
||||
eNewActivity = ACT_IDLE_ANGRY;
|
||||
}
|
||||
// keep aiming a little longer than normal since the shot takes so long and there's no good way to do a transitions between movement types :/
|
||||
else if ( eNewActivity == ACT_WALK )
|
||||
{
|
||||
eNewActivity = ACT_WALK_AIM;
|
||||
}
|
||||
else if ( eNewActivity == ACT_RUN )
|
||||
{
|
||||
eNewActivity = ACT_RUN_AIM;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We need these so that we can pick up the shotgun to throw it in the balcony scene
|
||||
if ( eNewActivity == ACT_IDLE_ANGRY_SHOTGUN )
|
||||
{
|
||||
eNewActivity = ACT_IDLE_ANGRY_SMG1;
|
||||
}
|
||||
else if ( eNewActivity == ACT_WALK_AIM_SHOTGUN )
|
||||
{
|
||||
eNewActivity = ACT_WALK_AIM_RIFLE;
|
||||
}
|
||||
else if ( eNewActivity == ACT_RUN_AIM_SHOTGUN )
|
||||
{
|
||||
eNewActivity = ACT_RUN_AIM_RIFLE;
|
||||
}
|
||||
else if ( eNewActivity == ACT_RANGE_ATTACK_SHOTGUN_LOW )
|
||||
{
|
||||
return ACT_RANGE_ATTACK_SMG1_LOW;
|
||||
}
|
||||
|
||||
return eNewActivity;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNPC_Monk::Precache()
|
||||
{
|
||||
PrecacheModel( "models/Monk.mdl" );
|
||||
|
||||
PrecacheScriptSound( "NPC_Citizen.FootstepLeft" );
|
||||
PrecacheScriptSound( "NPC_Citizen.FootstepRight" );
|
||||
|
||||
BaseClass::Precache();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNPC_Monk::Spawn()
|
||||
{
|
||||
Precache();
|
||||
|
||||
BaseClass::Spawn();
|
||||
|
||||
SetModel( "models/Monk.mdl" );
|
||||
|
||||
SetHullType(HULL_HUMAN);
|
||||
SetHullSizeNormal();
|
||||
|
||||
SetSolid( SOLID_BBOX );
|
||||
AddSolidFlags( FSOLID_NOT_STANDABLE );
|
||||
SetMoveType( MOVETYPE_STEP );
|
||||
SetBloodColor( BLOOD_COLOR_RED );
|
||||
m_iHealth = 100;
|
||||
m_flFieldOfView = m_flFieldOfView = -0.707; // 270`
|
||||
m_NPCState = NPC_STATE_NONE;
|
||||
|
||||
m_HackedGunPos = Vector ( 0, 0, 55 );
|
||||
|
||||
CapabilitiesAdd( bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP | bits_CAP_MOVE_GROUND );
|
||||
CapabilitiesAdd( bits_CAP_USE_WEAPONS );
|
||||
CapabilitiesAdd( bits_CAP_ANIMATEDFACE );
|
||||
CapabilitiesAdd( bits_CAP_FRIENDLY_DMG_IMMUNE );
|
||||
CapabilitiesAdd( bits_CAP_AIM_GUN );
|
||||
CapabilitiesAdd( bits_CAP_MOVE_SHOOT );
|
||||
|
||||
NPCInit();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
void CNPC_Monk::PainSound( const CTakeDamageInfo &info )
|
||||
{
|
||||
SpeakIfAllowed( TLK_WOUND );
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
void CNPC_Monk::DeathSound( const CTakeDamageInfo &info )
|
||||
{
|
||||
// Sentences don't play on dead NPCs
|
||||
SentenceStop();
|
||||
|
||||
Speak( TLK_DEATH );
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
WeaponProficiency_t CNPC_Monk::CalcWeaponProficiency( CBaseCombatWeapon *pWeapon )
|
||||
{
|
||||
return WEAPON_PROFICIENCY_PERFECT;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
Vector CNPC_Monk::GetActualShootPosition( const Vector &shootOrigin )
|
||||
{
|
||||
return BaseClass::GetActualShootPosition( shootOrigin );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
Vector CNPC_Monk::GetActualShootTrajectory( const Vector &shootOrigin )
|
||||
{
|
||||
if( GetEnemy() && GetEnemy()->Classify() == CLASS_ZOMBIE )
|
||||
{
|
||||
Vector vecShootDir;
|
||||
|
||||
if( m_bPerfectAccuracy || random->RandomInt( 1, monk_headshot_freq.GetInt() ) == 1 )
|
||||
{
|
||||
vecShootDir = GetEnemy()->HeadTarget( shootOrigin ) - shootOrigin;
|
||||
}
|
||||
else
|
||||
{
|
||||
vecShootDir = GetEnemy()->BodyTarget( shootOrigin ) - shootOrigin;
|
||||
}
|
||||
|
||||
VectorNormalize( vecShootDir );
|
||||
return vecShootDir;
|
||||
}
|
||||
|
||||
return BaseClass::GetActualShootTrajectory( shootOrigin );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Input : pEvent -
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNPC_Monk::HandleAnimEvent( animevent_t *pEvent )
|
||||
{
|
||||
switch( pEvent->event )
|
||||
{
|
||||
case NPC_EVENT_LEFTFOOT:
|
||||
{
|
||||
EmitSound( "NPC_Citizen.FootstepLeft", pEvent->eventtime );
|
||||
}
|
||||
break;
|
||||
case NPC_EVENT_RIGHTFOOT:
|
||||
{
|
||||
EmitSound( "NPC_Citizen.FootstepRight", pEvent->eventtime );
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
BaseClass::HandleAnimEvent( pEvent );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
// Grigori tries to stand his ground until
|
||||
// enemies are very close.
|
||||
//-------------------------------------
|
||||
#define MONK_STAND_GROUND_HEIGHT 24.0
|
||||
bool CNPC_Monk::ShouldBackAway()
|
||||
{
|
||||
if( !GetEnemy() )
|
||||
return false;
|
||||
|
||||
if( GetAbsOrigin().z - GetEnemy()->GetAbsOrigin().z >= MONK_STAND_GROUND_HEIGHT )
|
||||
{
|
||||
// This is a fairly special case. Grigori looks better fighting from his assault points in the
|
||||
// elevated places of the Graveyard, so we prevent his back away behavior anytime he has a height
|
||||
// advantage on his enemy.
|
||||
return false;
|
||||
}
|
||||
|
||||
float flDist;
|
||||
flDist = ( GetAbsOrigin() - GetEnemy()->GetAbsOrigin() ).Length();
|
||||
|
||||
if( flDist <= 180 )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
bool CNPC_Monk::IsValidEnemy( CBaseEntity *pEnemy )
|
||||
{
|
||||
if ( BaseClass::IsValidEnemy( pEnemy ) && GetActiveWeapon() )
|
||||
{
|
||||
float flDist;
|
||||
|
||||
flDist = ( GetAbsOrigin() - pEnemy->GetAbsOrigin() ).Length();
|
||||
if( flDist <= GetActiveWeapon()->m_fMaxRange1 )
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
int CNPC_Monk::TranslateSchedule( int scheduleType )
|
||||
{
|
||||
switch( scheduleType )
|
||||
{
|
||||
case SCHED_MOVE_AWAY_FAIL:
|
||||
// Our first method of backing away failed. Try another.
|
||||
return SCHED_MONK_BACK_AWAY_FROM_ENEMY;
|
||||
break;
|
||||
|
||||
case SCHED_RANGE_ATTACK1:
|
||||
{
|
||||
if( ShouldBackAway() )
|
||||
{
|
||||
// Get some room, rely on move and shoot.
|
||||
return SCHED_MOVE_AWAY;
|
||||
}
|
||||
|
||||
return SCHED_MONK_RANGE_ATTACK1;
|
||||
}
|
||||
break;
|
||||
|
||||
case SCHED_HIDE_AND_RELOAD:
|
||||
case SCHED_RELOAD:
|
||||
if( ShouldBackAway() )
|
||||
{
|
||||
return SCHED_MONK_BACK_AWAY_AND_RELOAD;
|
||||
}
|
||||
|
||||
return SCHED_RELOAD;
|
||||
break;
|
||||
}
|
||||
|
||||
return BaseClass::TranslateSchedule( scheduleType );
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
void CNPC_Monk::PrescheduleThink()
|
||||
{
|
||||
BaseClass::PrescheduleThink();
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
int CNPC_Monk::SelectSchedule()
|
||||
{
|
||||
if( HasCondition( COND_HEAR_DANGER ) )
|
||||
{
|
||||
SpeakIfAllowed( TLK_DANGER );
|
||||
return SCHED_TAKE_COVER_FROM_BEST_SOUND;
|
||||
}
|
||||
|
||||
if ( HasCondition( COND_TALKER_PLAYER_DEAD ) && !m_bMournedPlayer && IsOkToSpeak() )
|
||||
{
|
||||
m_bMournedPlayer = true;
|
||||
Speak( TLK_IDLE );
|
||||
}
|
||||
|
||||
if( !BehaviorSelectSchedule() )
|
||||
{
|
||||
if ( HasCondition ( COND_NO_PRIMARY_AMMO ) )
|
||||
{
|
||||
return SCHED_HIDE_AND_RELOAD;
|
||||
}
|
||||
}
|
||||
|
||||
return BaseClass::SelectSchedule();
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
void CNPC_Monk::StartTask( const Task_t *pTask )
|
||||
{
|
||||
switch( pTask->iTask )
|
||||
{
|
||||
case TASK_RELOAD:
|
||||
{
|
||||
if ( GetActiveWeapon() && GetActiveWeapon()->HasPrimaryAmmo() )
|
||||
{
|
||||
// Don't reload if you have done so while moving (See BACK_AWAY_AND_RELOAD schedule).
|
||||
TaskComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
if( m_iNumZombies >= 2 && random->RandomInt( 1, 3 ) == 1 )
|
||||
{
|
||||
SpeakIfAllowed( TLK_ATTACKING );
|
||||
}
|
||||
|
||||
Activity reloadGesture = TranslateActivity( ACT_GESTURE_RELOAD );
|
||||
if ( reloadGesture != ACT_INVALID && IsPlayingGesture( reloadGesture ) )
|
||||
{
|
||||
ResetIdealActivity( ACT_IDLE );
|
||||
return;
|
||||
}
|
||||
|
||||
BaseClass::StartTask( pTask );
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
BaseClass::StartTask( pTask );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CNPC_Monk::RunTask( const Task_t *pTask )
|
||||
{
|
||||
switch( pTask->iTask )
|
||||
{
|
||||
case TASK_RELOAD:
|
||||
{
|
||||
Activity reloadGesture = TranslateActivity( ACT_GESTURE_RELOAD );
|
||||
if ( GetIdealActivity() != ACT_RELOAD && reloadGesture != ACT_INVALID )
|
||||
{
|
||||
if ( !IsPlayingGesture( reloadGesture ) )
|
||||
{
|
||||
if ( GetShotRegulator() )
|
||||
{
|
||||
GetShotRegulator()->Reset( false );
|
||||
}
|
||||
|
||||
TaskComplete();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
BaseClass::RunTask( pTask );
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
BaseClass::RunTask( pTask );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNPC_Monk::GatherConditions()
|
||||
{
|
||||
BaseClass::GatherConditions();
|
||||
|
||||
// Build my zombie danger index!
|
||||
m_iNumZombies = 0;
|
||||
m_iDangerousZombies = 0;
|
||||
|
||||
AISightIter_t iter;
|
||||
CBaseEntity *pSightEnt;
|
||||
pSightEnt = GetSenses()->GetFirstSeenEntity( &iter );
|
||||
while( pSightEnt )
|
||||
{
|
||||
if( pSightEnt->Classify() == CLASS_ZOMBIE && pSightEnt->IsAlive() )
|
||||
{
|
||||
// Is this zombie coming for me?
|
||||
CAI_BaseNPC *pZombie = dynamic_cast<CAI_BaseNPC*>(pSightEnt);
|
||||
|
||||
if( pZombie && pZombie->GetEnemy() == this )
|
||||
{
|
||||
m_iNumZombies++;
|
||||
|
||||
// if this zombie is close enough to attack, add him to the zombie danger!
|
||||
float flDist;
|
||||
|
||||
flDist = (pZombie->GetAbsOrigin() - GetAbsOrigin()).Length2DSqr();
|
||||
|
||||
if( flDist <= 128.0f * 128.0f )
|
||||
{
|
||||
m_iDangerousZombies++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pSightEnt = GetSenses()->GetNextSeenEntity( &iter );
|
||||
}
|
||||
|
||||
if( m_iDangerousZombies >= 3 || (GetEnemy() && GetHealth() < 25) )
|
||||
{
|
||||
// I see many zombies, or I'm quite injured.
|
||||
SpeakIfAllowed( TLK_HELP_ME );
|
||||
}
|
||||
|
||||
// NOTE!!!!!! This code assumes grigori is using annabelle!
|
||||
ClearCondition(COND_LOW_PRIMARY_AMMO);
|
||||
if ( GetActiveWeapon() )
|
||||
{
|
||||
if ( GetActiveWeapon()->UsesPrimaryAmmo() )
|
||||
{
|
||||
if (!GetActiveWeapon()->HasPrimaryAmmo() )
|
||||
{
|
||||
SetCondition(COND_NO_PRIMARY_AMMO);
|
||||
}
|
||||
else if ( m_NPCState != NPC_STATE_COMBAT && GetActiveWeapon()->UsesClipsForAmmo1() && GetActiveWeapon()->Clip1() < 2 )
|
||||
{
|
||||
// Don't send a low ammo message unless we're not in combat.
|
||||
SetCondition(COND_LOW_PRIMARY_AMMO);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNPC_Monk::PassesDamageFilter( const CTakeDamageInfo &info )
|
||||
{
|
||||
if ( info.GetAttacker()->ClassMatches( "npc_headcrab_black" ) || info.GetAttacker()->ClassMatches( "npc_headcrab_poison" ) )
|
||||
return false;
|
||||
|
||||
return BaseClass::PassesDamageFilter( info );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNPC_Monk::OnKilledNPC( CBaseCombatCharacter *pKilled )
|
||||
{
|
||||
if ( !pKilled )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ( pKilled->Classify() == CLASS_ZOMBIE )
|
||||
{
|
||||
// Don't speak if the gun is empty, cause grigori will want to speak while he's reloading.
|
||||
if ( GetActiveWeapon() )
|
||||
{
|
||||
if ( GetActiveWeapon()->UsesPrimaryAmmo() && !GetActiveWeapon()->HasPrimaryAmmo() )
|
||||
{
|
||||
// Gun is empty. I'm about to reload.
|
||||
if( m_iNumZombies >= 2 )
|
||||
{
|
||||
// Don't talk about killing a single zombie if there are more coming.
|
||||
// the reload behavior will say "come to me, children", etc.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( m_iNumZombies == 1 || random->RandomInt( 1, 3 ) == 1 )
|
||||
{
|
||||
SpeakIfAllowed( TLK_ENEMY_DEAD );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
int CNPC_Monk::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
|
||||
{
|
||||
if( failedSchedule == SCHED_MONK_BACK_AWAY_FROM_ENEMY )
|
||||
{
|
||||
if( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
|
||||
{
|
||||
// Most likely backed into a corner. Just blaze away.
|
||||
return SCHED_MONK_RANGE_ATTACK1;
|
||||
}
|
||||
}
|
||||
|
||||
return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNPC_Monk::IsJumpLegal( const Vector &startPos, const Vector &apex, const Vector &endPos ) const
|
||||
{
|
||||
if ( startPos.z - endPos.z < 0 )
|
||||
return false;
|
||||
return BaseClass::IsJumpLegal( startPos, apex, endPos );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Every shot's a headshot. Useful for scripted Grigoris
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNPC_Monk::InputPerfectAccuracyOn( inputdata_t &inputdata )
|
||||
{
|
||||
m_bPerfectAccuracy = true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Turn off perfect accuracy.
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNPC_Monk::InputPerfectAccuracyOff( inputdata_t &inputdata )
|
||||
{
|
||||
m_bPerfectAccuracy = false;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// CNPC_Monk Schedules
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
AI_BEGIN_CUSTOM_NPC( npc_monk, CNPC_Monk )
|
||||
|
||||
DECLARE_ACTIVITY( ACT_MONK_GUN_IDLE )
|
||||
|
||||
DEFINE_SCHEDULE
|
||||
(
|
||||
SCHED_MONK_RANGE_ATTACK1,
|
||||
|
||||
" Tasks"
|
||||
" TASK_STOP_MOVING 0"
|
||||
" TASK_FACE_ENEMY 0"
|
||||
" TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
|
||||
" TASK_RANGE_ATTACK1 0"
|
||||
""
|
||||
" Interrupts"
|
||||
" COND_HEAVY_DAMAGE"
|
||||
" COND_ENEMY_OCCLUDED"
|
||||
" COND_HEAR_DANGER"
|
||||
" COND_WEAPON_BLOCKED_BY_FRIEND"
|
||||
" COND_WEAPON_SIGHT_OCCLUDED"
|
||||
)
|
||||
|
||||
DEFINE_SCHEDULE
|
||||
(
|
||||
SCHED_MONK_BACK_AWAY_FROM_ENEMY,
|
||||
|
||||
" Tasks"
|
||||
" TASK_STOP_MOVING 0"
|
||||
" TASK_STORE_ENEMY_POSITION_IN_SAVEPOSITION 0"
|
||||
" TASK_FIND_BACKAWAY_FROM_SAVEPOSITION 0"
|
||||
" TASK_WALK_PATH_TIMED 4.0"
|
||||
" TASK_WAIT_FOR_MOVEMENT 0"
|
||||
""
|
||||
" Interrupts"
|
||||
" COND_NEW_ENEMY"
|
||||
" COND_ENEMY_DEAD"
|
||||
);
|
||||
|
||||
DEFINE_SCHEDULE
|
||||
(
|
||||
SCHED_MONK_BACK_AWAY_AND_RELOAD,
|
||||
|
||||
" Tasks"
|
||||
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_MONK_NORMAL_RELOAD"
|
||||
" TASK_STOP_MOVING 0"
|
||||
" TASK_STORE_ENEMY_POSITION_IN_SAVEPOSITION 0"
|
||||
" TASK_FIND_BACKAWAY_FROM_SAVEPOSITION 0"
|
||||
" TASK_WALK_PATH_TIMED 2.0"
|
||||
" TASK_WAIT_FOR_MOVEMENT 0"
|
||||
" TASK_STOP_MOVING 0"
|
||||
" TASK_RELOAD 0"
|
||||
""
|
||||
" Interrupts"
|
||||
" COND_ENEMY_DEAD"
|
||||
);
|
||||
|
||||
DEFINE_SCHEDULE
|
||||
(
|
||||
SCHED_MONK_NORMAL_RELOAD,
|
||||
|
||||
" Tasks"
|
||||
" TASK_STOP_MOVING 0"
|
||||
" TASK_RELOAD 0"
|
||||
""
|
||||
" Interrupts"
|
||||
" COND_HEAR_DANGER"
|
||||
);
|
||||
|
||||
|
||||
AI_END_CUSTOM_NPC()
|
||||
Reference in New Issue
Block a user