aboutsummaryrefslogtreecommitdiff
path: root/sp/src/game/server/hl2/npc_monk.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /sp/src/game/server/hl2/npc_monk.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'sp/src/game/server/hl2/npc_monk.cpp')
-rw-r--r--sp/src/game/server/hl2/npc_monk.cpp776
1 files changed, 776 insertions, 0 deletions
diff --git a/sp/src/game/server/hl2/npc_monk.cpp b/sp/src/game/server/hl2/npc_monk.cpp
new file mode 100644
index 00000000..31bb70f4
--- /dev/null
+++ b/sp/src/game/server/hl2/npc_monk.cpp
@@ -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()