You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
951 lines
23 KiB
951 lines
23 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//=============================================================================// |
|
#include "cbase.h" |
|
#include "ai_default.h" |
|
#include "ai_task.h" |
|
#include "ai_schedule.h" |
|
#include "ai_node.h" |
|
#include "ai_hull.h" |
|
#include "ai_hint.h" |
|
#include "ai_memory.h" |
|
#include "ai_route.h" |
|
#include "ai_motor.h" |
|
#include "soundent.h" |
|
#include "game.h" |
|
#include "npcevent.h" |
|
#include "entitylist.h" |
|
#include "activitylist.h" |
|
#include "animation.h" |
|
#include "basecombatweapon.h" |
|
#include "IEffects.h" |
|
#include "vstdlib/random.h" |
|
#include "engine/IEngineSound.h" |
|
#include "ammodef.h" |
|
#include "util.h" |
|
#include "hl1_ai_basenpc.h" |
|
#include "hl1_basegrenade.h" |
|
#include "movevars_shared.h" |
|
#include "ai_basenpc.h" |
|
|
|
|
|
ConVar sk_hassassin_health( "sk_hassassin_health", "50" ); |
|
|
|
//========================================================= |
|
// monster-specific schedule types |
|
//========================================================= |
|
enum |
|
{ |
|
SCHED_ASSASSIN_EXPOSED = LAST_SHARED_SCHEDULE,// cover was blown. |
|
SCHED_ASSASSIN_JUMP, // fly through the air |
|
SCHED_ASSASSIN_JUMP_ATTACK, // fly through the air and shoot |
|
SCHED_ASSASSIN_JUMP_LAND, // hit and run away |
|
SCHED_ASSASSIN_FAIL, |
|
SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY1, |
|
SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY2, |
|
SCHED_ASSASSIN_TAKE_COVER_FROM_BEST_SOUND, |
|
SCHED_ASSASSIN_HIDE, |
|
SCHED_ASSASSIN_HUNT, |
|
}; |
|
|
|
Activity ACT_ASSASSIN_FLY_UP; |
|
Activity ACT_ASSASSIN_FLY_ATTACK; |
|
Activity ACT_ASSASSIN_FLY_DOWN; |
|
|
|
//========================================================= |
|
// monster-specific tasks |
|
//========================================================= |
|
|
|
enum |
|
{ |
|
TASK_ASSASSIN_FALL_TO_GROUND = LAST_SHARED_TASK + 1, // falling and waiting to hit ground |
|
}; |
|
|
|
|
|
//========================================================= |
|
// Monster's Anim Events Go Here |
|
//========================================================= |
|
#define ASSASSIN_AE_SHOOT1 1 |
|
#define ASSASSIN_AE_TOSS1 2 |
|
#define ASSASSIN_AE_JUMP 3 |
|
|
|
|
|
#define MEMORY_BADJUMP bits_MEMORY_CUSTOM1 |
|
|
|
class CNPC_HAssassin : public CHL1BaseNPC |
|
{ |
|
DECLARE_CLASS( CNPC_HAssassin, CHL1BaseNPC ); |
|
|
|
public: |
|
void Spawn( void ); |
|
void Precache( void ); |
|
|
|
int TranslateSchedule( int scheduleType ); |
|
|
|
void HandleAnimEvent( animevent_t *pEvent ); |
|
float MaxYawSpeed() { return 360.0f; } |
|
|
|
void Shoot ( void ); |
|
|
|
int MeleeAttack1Conditions ( float flDot, float flDist ); |
|
int RangeAttack1Conditions ( float flDot, float flDist ); |
|
int RangeAttack2Conditions ( float flDot, float flDist ); |
|
|
|
int SelectSchedule ( void ); |
|
|
|
void RunTask ( const Task_t *pTask ); |
|
void StartTask ( const Task_t *pTask ); |
|
|
|
Class_T Classify ( void ); |
|
|
|
int GetSoundInterests( void ); |
|
|
|
void RunAI( void ); |
|
|
|
float m_flLastShot; |
|
float m_flDiviation; |
|
|
|
float m_flNextJump; |
|
Vector m_vecJumpVelocity; |
|
|
|
float m_flNextGrenadeCheck; |
|
Vector m_vecTossVelocity; |
|
bool m_fThrowGrenade; |
|
|
|
int m_iTargetRanderamt; |
|
|
|
int m_iFrustration; |
|
|
|
int m_iAmmoType; |
|
|
|
public: |
|
DECLARE_DATADESC(); |
|
DEFINE_CUSTOM_AI; |
|
|
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( monster_human_assassin, CNPC_HAssassin ); |
|
|
|
BEGIN_DATADESC( CNPC_HAssassin ) |
|
DEFINE_FIELD( m_flLastShot, FIELD_TIME ), |
|
DEFINE_FIELD( m_flDiviation, FIELD_FLOAT ), |
|
|
|
DEFINE_FIELD( m_flNextJump, FIELD_TIME ), |
|
DEFINE_FIELD( m_vecJumpVelocity, FIELD_VECTOR ), |
|
|
|
DEFINE_FIELD( m_flNextGrenadeCheck, FIELD_TIME ), |
|
DEFINE_FIELD( m_vecTossVelocity, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_fThrowGrenade, FIELD_BOOLEAN ), |
|
|
|
DEFINE_FIELD( m_iTargetRanderamt, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_iFrustration, FIELD_INTEGER ), |
|
|
|
//DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ), |
|
|
|
END_DATADESC() |
|
|
|
//========================================================= |
|
// Spawn |
|
//========================================================= |
|
void CNPC_HAssassin::Spawn() |
|
{ |
|
Precache( ); |
|
|
|
SetModel( "models/hassassin.mdl"); |
|
|
|
SetHullType(HULL_HUMAN); |
|
SetHullSizeNormal(); |
|
|
|
|
|
SetNavType ( NAV_GROUND ); |
|
SetSolid( SOLID_BBOX ); |
|
AddSolidFlags( FSOLID_NOT_STANDABLE ); |
|
SetMoveType( MOVETYPE_STEP ); |
|
m_bloodColor = BLOOD_COLOR_RED; |
|
ClearEffects(); |
|
m_iHealth = sk_hassassin_health.GetFloat(); |
|
m_flFieldOfView = VIEW_FIELD_WIDE; // indicates the width of this monster's forward view cone ( as a dotproduct result ) |
|
m_NPCState = NPC_STATE_NONE; |
|
|
|
m_HackedGunPos = Vector( 0, 24, 48 ); |
|
|
|
m_iTargetRanderamt = 20; |
|
SetRenderColor( 255, 255, 255, 20 ); |
|
m_nRenderMode = kRenderTransTexture; |
|
|
|
CapabilitiesClear(); |
|
CapabilitiesAdd( bits_CAP_MOVE_GROUND ); |
|
CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 | bits_CAP_INNATE_MELEE_ATTACK1 ); |
|
|
|
NPCInit(); |
|
} |
|
|
|
//========================================================= |
|
// Precache - precaches all resources this monster needs |
|
//========================================================= |
|
void CNPC_HAssassin::Precache() |
|
{ |
|
m_iAmmoType = GetAmmoDef()->Index("9mmRound"); |
|
|
|
PrecacheModel("models/hassassin.mdl"); |
|
|
|
UTIL_PrecacheOther( "npc_handgrenade" ); |
|
|
|
PrecacheScriptSound( "HAssassin.Shot" ); |
|
PrecacheScriptSound( "HAssassin.Beamsound" ); |
|
PrecacheScriptSound( "HAssassin.Footstep" ); |
|
} |
|
|
|
int CNPC_HAssassin::GetSoundInterests( void ) |
|
{ |
|
return SOUND_WORLD | |
|
SOUND_COMBAT | |
|
SOUND_PLAYER | |
|
SOUND_DANGER; |
|
} |
|
|
|
Class_T CNPC_HAssassin::Classify ( void ) |
|
{ |
|
return CLASS_HUMAN_MILITARY; |
|
} |
|
|
|
//========================================================= |
|
// CheckMeleeAttack1 - jump like crazy if the enemy gets too close. |
|
//========================================================= |
|
int CNPC_HAssassin::MeleeAttack1Conditions ( float flDot, float flDist ) |
|
{ |
|
if ( m_flNextJump < gpGlobals->curtime && ( flDist <= 128 || HasMemory( MEMORY_BADJUMP )) && GetEnemy() != NULL ) |
|
{ |
|
trace_t tr; |
|
|
|
Vector vecMin = Vector( random->RandomFloat( 0, -64), random->RandomFloat( 0, -64 ), 0 ); |
|
Vector vecMax = Vector( random->RandomFloat( 0, 64), random->RandomFloat( 0, 64 ), 160 ); |
|
|
|
Vector vecDest = GetAbsOrigin() + Vector( random->RandomFloat( -64, 64), random->RandomFloat( -64, 64 ), 160 ); |
|
|
|
UTIL_TraceHull( GetAbsOrigin() + Vector( 0, 0, 36 ), GetAbsOrigin() + Vector( 0, 0, 36 ), vecMin, vecMax, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
//NDebugOverlay::Box( GetAbsOrigin() + Vector( 0, 0, 36 ), vecMin, vecMax, 0,0, 255, 0, 2.0 ); |
|
|
|
if ( tr.startsolid || tr.fraction < 1.0) |
|
{ |
|
return COND_TOO_CLOSE_TO_ATTACK; |
|
} |
|
|
|
float flGravity = GetCurrentGravity(); |
|
|
|
float time = sqrt( 160 / (0.5 * flGravity)); |
|
float speed = flGravity * time / 160; |
|
m_vecJumpVelocity = ( vecDest - GetAbsOrigin() ) * speed; |
|
|
|
return COND_CAN_MELEE_ATTACK1; |
|
} |
|
|
|
if ( flDist > 128 ) |
|
return COND_TOO_FAR_TO_ATTACK; |
|
|
|
return COND_NONE; |
|
} |
|
|
|
//========================================================= |
|
// CheckRangeAttack1 - drop a cap in their ass |
|
// |
|
//========================================================= |
|
int CNPC_HAssassin::RangeAttack1Conditions ( float flDot, float flDist ) |
|
{ |
|
if ( !HasCondition( COND_ENEMY_OCCLUDED ) && flDist > 64 && flDist <= 2048 ) |
|
{ |
|
trace_t tr; |
|
|
|
Vector vecSrc = GetAbsOrigin() + m_HackedGunPos; |
|
|
|
// verify that a bullet fired from the gun will hit the enemy before the world. |
|
UTIL_TraceLine( vecSrc, GetEnemy()->BodyTarget(vecSrc), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr); |
|
|
|
if ( tr.fraction == 1.0 || tr.m_pEnt == GetEnemy() ) |
|
{ |
|
return COND_CAN_RANGE_ATTACK1; |
|
} |
|
} |
|
|
|
return COND_NONE; |
|
} |
|
|
|
//========================================================= |
|
// CheckRangeAttack2 - toss grenade is enemy gets in the way and is too close. |
|
//========================================================= |
|
int CNPC_HAssassin::RangeAttack2Conditions ( float flDot, float flDist ) |
|
{ |
|
m_fThrowGrenade = false; |
|
if ( !FBitSet ( GetEnemy()->GetFlags(), FL_ONGROUND ) ) |
|
{ |
|
// don't throw grenades at anything that isn't on the ground! |
|
return COND_NONE; |
|
} |
|
|
|
// don't get grenade happy unless the player starts to piss you off |
|
if ( m_iFrustration <= 2) |
|
return COND_NONE; |
|
|
|
if ( m_flNextGrenadeCheck < gpGlobals->curtime && !HasCondition( COND_ENEMY_OCCLUDED ) && flDist <= 512 ) |
|
{ |
|
Vector vTossPos; |
|
QAngle vAngles; |
|
|
|
GetAttachment( "grenadehand", vTossPos, vAngles ); |
|
|
|
Vector vecToss = VecCheckThrow( this, vTossPos, GetEnemy()->WorldSpaceCenter(), flDist, 0.5 ); // use dist as speed to get there in 1 second |
|
|
|
if ( vecToss != vec3_origin ) |
|
{ |
|
m_vecTossVelocity = vecToss; |
|
|
|
// throw a hand grenade |
|
m_fThrowGrenade = TRUE; |
|
|
|
return COND_CAN_RANGE_ATTACK2; |
|
} |
|
} |
|
|
|
return COND_NONE; |
|
} |
|
|
|
//========================================================= |
|
// StartTask |
|
//========================================================= |
|
void CNPC_HAssassin::StartTask ( const Task_t *pTask ) |
|
{ |
|
switch ( pTask->iTask ) |
|
{ |
|
case TASK_RANGE_ATTACK2: |
|
if (!m_fThrowGrenade) |
|
{ |
|
TaskComplete( ); |
|
} |
|
else |
|
{ |
|
BaseClass::StartTask ( pTask ); |
|
} |
|
break; |
|
case TASK_ASSASSIN_FALL_TO_GROUND: |
|
m_flWaitFinished = gpGlobals->curtime + 2.0f; |
|
break; |
|
default: |
|
BaseClass::StartTask ( pTask ); |
|
break; |
|
} |
|
} |
|
|
|
|
|
//========================================================= |
|
// RunTask |
|
//========================================================= |
|
void CNPC_HAssassin::RunTask ( const Task_t *pTask ) |
|
{ |
|
switch ( pTask->iTask ) |
|
{ |
|
case TASK_ASSASSIN_FALL_TO_GROUND: |
|
GetMotor()->SetIdealYawAndUpdate( GetEnemyLKP() ); |
|
|
|
if ( IsSequenceFinished() ) |
|
{ |
|
if ( GetAbsVelocity().z > 0) |
|
{ |
|
SetActivity( ACT_ASSASSIN_FLY_UP ); |
|
} |
|
else if ( HasCondition ( COND_SEE_ENEMY )) |
|
{ |
|
SetActivity( ACT_ASSASSIN_FLY_ATTACK ); |
|
SetCycle( 0 ); |
|
} |
|
else |
|
{ |
|
SetActivity( ACT_ASSASSIN_FLY_DOWN ); |
|
SetCycle( 0 ); |
|
} |
|
|
|
ResetSequenceInfo( ); |
|
} |
|
|
|
if ( GetFlags() & FL_ONGROUND) |
|
{ |
|
TaskComplete( ); |
|
} |
|
else if( gpGlobals->curtime > m_flWaitFinished || GetAbsVelocity().z == 0.0 ) |
|
{ |
|
// I've waited two seconds and haven't hit the ground. Try to force it. |
|
trace_t trace; |
|
UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 1 ), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &trace ); |
|
|
|
if( trace.DidHitWorld() ) |
|
{ |
|
SetGroundEntity( trace.m_pEnt ); |
|
} |
|
else |
|
{ |
|
// Try again in a couple of seconds. |
|
m_flWaitFinished = gpGlobals->curtime + 2.0f; |
|
} |
|
} |
|
break; |
|
default: |
|
BaseClass::RunTask ( pTask ); |
|
break; |
|
} |
|
} |
|
|
|
//========================================================= |
|
// GetSchedule - Decides which type of schedule best suits |
|
// the monster's current state and conditions. Then calls |
|
// monster's member function to get a pointer to a schedule |
|
// of the proper type. |
|
//========================================================= |
|
int CNPC_HAssassin::SelectSchedule ( void ) |
|
{ |
|
switch ( m_NPCState ) |
|
{ |
|
case NPC_STATE_IDLE: |
|
case NPC_STATE_ALERT: |
|
{ |
|
if ( HasCondition ( COND_HEAR_DANGER ) || HasCondition ( COND_HEAR_COMBAT ) ) |
|
{ |
|
if ( HasCondition ( COND_HEAR_DANGER ) ) |
|
return SCHED_TAKE_COVER_FROM_BEST_SOUND; |
|
|
|
else |
|
return SCHED_INVESTIGATE_SOUND; |
|
} |
|
} |
|
break; |
|
|
|
case NPC_STATE_COMBAT: |
|
{ |
|
// dead enemy |
|
if ( HasCondition( COND_ENEMY_DEAD ) ) |
|
{ |
|
// call base class, all code to handle dead enemies is centralized there. |
|
return BaseClass::SelectSchedule(); |
|
} |
|
|
|
// flying? |
|
if ( GetMoveType() == MOVETYPE_FLYGRAVITY ) |
|
{ |
|
if ( GetFlags() & FL_ONGROUND ) |
|
{ |
|
//Msg( "landed\n" ); |
|
// just landed |
|
SetMoveType( MOVETYPE_STEP ); |
|
return SCHED_ASSASSIN_JUMP_LAND; |
|
} |
|
else |
|
{ |
|
//Msg("jump\n"); |
|
// jump or jump/shoot |
|
if ( m_NPCState == NPC_STATE_COMBAT ) |
|
return SCHED_ASSASSIN_JUMP; |
|
else |
|
return SCHED_ASSASSIN_JUMP_ATTACK; |
|
} |
|
} |
|
|
|
if ( HasCondition ( COND_HEAR_DANGER ) ) |
|
{ |
|
return SCHED_TAKE_COVER_FROM_BEST_SOUND; |
|
} |
|
|
|
if ( HasCondition ( COND_LIGHT_DAMAGE ) ) |
|
{ |
|
m_iFrustration++; |
|
} |
|
if ( HasCondition ( COND_HEAVY_DAMAGE ) ) |
|
{ |
|
m_iFrustration++; |
|
} |
|
|
|
// jump player! |
|
if ( HasCondition ( COND_CAN_MELEE_ATTACK1 ) ) |
|
{ |
|
//Msg( "melee attack 1\n"); |
|
return SCHED_MELEE_ATTACK1; |
|
} |
|
|
|
// throw grenade |
|
if ( HasCondition ( COND_CAN_RANGE_ATTACK2 ) ) |
|
{ |
|
//Msg( "range attack 2\n"); |
|
return SCHED_RANGE_ATTACK2; |
|
} |
|
|
|
// spotted |
|
if ( HasCondition ( COND_SEE_ENEMY ) && HasCondition ( COND_ENEMY_FACING_ME ) ) |
|
{ |
|
//Msg("exposed\n"); |
|
m_iFrustration++; |
|
return SCHED_ASSASSIN_EXPOSED; |
|
} |
|
|
|
// can attack |
|
if ( HasCondition ( COND_CAN_RANGE_ATTACK1 ) ) |
|
{ |
|
//Msg( "range attack 1\n" ); |
|
m_iFrustration = 0; |
|
return SCHED_RANGE_ATTACK1; |
|
} |
|
|
|
if ( HasCondition ( COND_SEE_ENEMY ) ) |
|
{ |
|
//Msg( "face\n"); |
|
return SCHED_COMBAT_FACE; |
|
} |
|
|
|
// new enemy |
|
if ( HasCondition ( COND_NEW_ENEMY ) ) |
|
{ |
|
//Msg( "take cover\n"); |
|
return SCHED_TAKE_COVER_FROM_ENEMY; |
|
} |
|
|
|
// ALERT( at_console, "stand\n"); |
|
return SCHED_ALERT_STAND; |
|
} |
|
break; |
|
} |
|
|
|
return BaseClass::SelectSchedule(); |
|
} |
|
|
|
//========================================================= |
|
// HandleAnimEvent - catches the monster-specific messages |
|
// that occur when tagged animation frames are played. |
|
// |
|
// Returns number of events handled, 0 if none. |
|
//========================================================= |
|
void CNPC_HAssassin::HandleAnimEvent( animevent_t *pEvent ) |
|
{ |
|
switch( pEvent->event ) |
|
{ |
|
case ASSASSIN_AE_SHOOT1: |
|
Shoot( ); |
|
break; |
|
case ASSASSIN_AE_TOSS1: |
|
{ |
|
Vector vTossPos; |
|
QAngle vAngles; |
|
|
|
GetAttachment( "grenadehand", vTossPos, vAngles ); |
|
|
|
CHandGrenade *pGrenade = (CHandGrenade*)Create( "grenade_hand", vTossPos, vec3_angle ); |
|
if ( pGrenade ) |
|
{ |
|
pGrenade->ShootTimed( this, m_vecTossVelocity, 2.0 ); |
|
} |
|
|
|
m_flNextGrenadeCheck = gpGlobals->curtime + 6;// wait six seconds before even looking again to see if a grenade can be thrown. |
|
m_fThrowGrenade = FALSE; |
|
// !!!LATER - when in a group, only try to throw grenade if ordered. |
|
} |
|
break; |
|
case ASSASSIN_AE_JUMP: |
|
{ |
|
SetMoveType( MOVETYPE_FLYGRAVITY ); |
|
SetGroundEntity( NULL ); |
|
SetAbsVelocity( m_vecJumpVelocity ); |
|
m_flNextJump = gpGlobals->curtime + 3.0; |
|
} |
|
return; |
|
default: |
|
BaseClass::HandleAnimEvent( pEvent ); |
|
break; |
|
} |
|
} |
|
|
|
|
|
|
|
//========================================================= |
|
// Shoot |
|
//========================================================= |
|
void CNPC_HAssassin::Shoot ( void ) |
|
{ |
|
Vector vForward, vRight, vUp; |
|
Vector vecShootOrigin; |
|
QAngle vAngles; |
|
|
|
if ( GetEnemy() == NULL) |
|
{ |
|
return; |
|
} |
|
|
|
GetAttachment( "guntip", vecShootOrigin, vAngles ); |
|
|
|
Vector vecShootDir = GetShootEnemyDir( vecShootOrigin ); |
|
|
|
if (m_flLastShot + 2 < gpGlobals->curtime) |
|
{ |
|
m_flDiviation = 0.10; |
|
} |
|
else |
|
{ |
|
m_flDiviation -= 0.01; |
|
if (m_flDiviation < 0.02) |
|
m_flDiviation = 0.02; |
|
} |
|
m_flLastShot = gpGlobals->curtime; |
|
|
|
AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp ); |
|
|
|
Vector vecShellVelocity = vRight * random->RandomFloat(40,90) + vUp * random->RandomFloat(75,200) + vForward * random->RandomFloat(-40, 40); |
|
EjectShell( GetAbsOrigin() + vUp * 32 + vForward * 12, vecShellVelocity, GetAbsAngles().y, 0 ); |
|
FireBullets( 1, vecShootOrigin, vecShootDir, Vector( m_flDiviation, m_flDiviation, m_flDiviation ), 2048, m_iAmmoType ); // shoot +-8 degrees |
|
|
|
//NDebugOverlay::Line( vecShootOrigin, vecShootOrigin + vecShootDir * 2048, 255, 0, 0, true, 2.0 ); |
|
|
|
CPASAttenuationFilter filter( this ); |
|
EmitSound( filter, entindex(), "HAssassin.Shot" ); |
|
|
|
DoMuzzleFlash(); |
|
|
|
VectorAngles( vecShootDir, vAngles ); |
|
SetPoseParameter( "shoot", vecShootDir.x ); |
|
|
|
m_cAmmoLoaded--; |
|
} |
|
|
|
//========================================================= |
|
//========================================================= |
|
int CNPC_HAssassin::TranslateSchedule ( int scheduleType ) |
|
{ |
|
// Msg( "%d\n", m_iFrustration ); |
|
switch ( scheduleType ) |
|
{ |
|
case SCHED_TAKE_COVER_FROM_ENEMY: |
|
|
|
if ( m_iHealth > 30 ) |
|
return SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY1; |
|
else |
|
return SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY2; |
|
|
|
case SCHED_TAKE_COVER_FROM_BEST_SOUND: |
|
return SCHED_ASSASSIN_TAKE_COVER_FROM_BEST_SOUND; |
|
case SCHED_FAIL: |
|
|
|
if ( m_NPCState == NPC_STATE_COMBAT ) |
|
return SCHED_ASSASSIN_FAIL; |
|
|
|
break; |
|
case SCHED_ALERT_STAND: |
|
|
|
if ( m_NPCState == NPC_STATE_COMBAT ) |
|
return SCHED_ASSASSIN_HIDE; |
|
|
|
break; |
|
//case SCHED_CHASE_ENEMY: |
|
// return SCHED_ASSASSIN_HUNT; |
|
|
|
case SCHED_MELEE_ATTACK1: |
|
|
|
if ( GetFlags() & FL_ONGROUND) |
|
{ |
|
if (m_flNextJump > gpGlobals->curtime) |
|
{ |
|
// can't jump yet, go ahead and fail |
|
return SCHED_ASSASSIN_FAIL; |
|
} |
|
else |
|
{ |
|
return SCHED_ASSASSIN_JUMP; |
|
} |
|
} |
|
else |
|
{ |
|
return SCHED_ASSASSIN_JUMP_ATTACK; |
|
} |
|
} |
|
|
|
return BaseClass::TranslateSchedule( scheduleType ); |
|
} |
|
|
|
//========================================================= |
|
// RunAI |
|
//========================================================= |
|
void CNPC_HAssassin::RunAI( void ) |
|
{ |
|
BaseClass::RunAI(); |
|
|
|
// always visible if moving |
|
// always visible is not on hard |
|
if (g_iSkillLevel != SKILL_HARD || GetEnemy() == NULL || m_lifeState == LIFE_DEAD || GetActivity() == ACT_RUN || GetActivity() == ACT_WALK || !(GetFlags() & FL_ONGROUND)) |
|
m_iTargetRanderamt = 255; |
|
else |
|
m_iTargetRanderamt = 20; |
|
|
|
CPASAttenuationFilter filter( this ); |
|
|
|
if ( GetRenderColor().a > m_iTargetRanderamt) |
|
{ |
|
if ( GetRenderColor().a == 255) |
|
{ |
|
EmitSound( filter, entindex(), "HAssassin.Beamsound" ); |
|
} |
|
|
|
SetRenderColorA( MAX( GetRenderColor().a - 50, m_iTargetRanderamt ) ); |
|
m_nRenderMode = kRenderTransTexture; |
|
} |
|
else if ( GetRenderColor().a < m_iTargetRanderamt) |
|
{ |
|
SetRenderColorA ( MIN( GetRenderColor().a + 50, m_iTargetRanderamt ) ); |
|
if (GetRenderColor().a == 255) |
|
m_nRenderMode = kRenderNormal; |
|
} |
|
|
|
if ( GetActivity() == ACT_RUN || GetActivity() == ACT_WALK) |
|
{ |
|
static int iStep = 0; |
|
iStep = ! iStep; |
|
if (iStep) |
|
{ |
|
EmitSound( filter, entindex(), "HAssassin.Footstep" ); |
|
} |
|
} |
|
} |
|
|
|
AI_BEGIN_CUSTOM_NPC( monster_human_assassin, CNPC_HAssassin ) |
|
|
|
DECLARE_TASK( TASK_ASSASSIN_FALL_TO_GROUND ) |
|
|
|
DECLARE_ACTIVITY( ACT_ASSASSIN_FLY_UP ) |
|
DECLARE_ACTIVITY( ACT_ASSASSIN_FLY_ATTACK ) |
|
DECLARE_ACTIVITY( ACT_ASSASSIN_FLY_DOWN ) |
|
|
|
//========================================================= |
|
// AI Schedules Specific to this monster |
|
//========================================================= |
|
|
|
//========================================================= |
|
// Enemy exposed assasin's cover |
|
//========================================================= |
|
|
|
//========================================================= |
|
// > SCHED_ASSASSIN_EXPOSED |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ASSASSIN_EXPOSED, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_RANGE_ATTACK1 0" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSASSIN_JUMP" |
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_TAKE_COVER_FROM_ENEMY" |
|
" " |
|
" Interrupts" |
|
" COND_CAN_MELEE_ATTACK1" |
|
|
|
) |
|
|
|
//========================================================= |
|
// > SCHED_ASSASSIN_JUMP |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ASSASSIN_JUMP, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_HOP" |
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_ASSASSIN_JUMP_ATTACK" |
|
" " |
|
" Interrupts" |
|
) |
|
|
|
//========================================================= |
|
// > SCHED_ASSASSIN_JUMP_ATTACK |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ASSASSIN_JUMP_ATTACK, |
|
|
|
" Tasks" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSASSIN_JUMP_LAND" |
|
" TASK_ASSASSIN_FALL_TO_GROUND 0" |
|
" " |
|
" Interrupts" |
|
) |
|
|
|
//========================================================= |
|
// > SCHED_ASSASSIN_JUMP_LAND |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ASSASSIN_JUMP_LAND, |
|
|
|
" Tasks" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSASSIN_EXPOSED" |
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" |
|
" TASK_REMEMBER MEMORY:CUSTOM1" |
|
" TASK_FIND_NODE_COVER_FROM_ENEMY 0" |
|
" TASK_RUN_PATH 0" |
|
" TASK_FORGET MEMORY:CUSTOM1" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" TASK_REMEMBER MEMORY:INCOVER" |
|
" TASK_FACE_ENEMY 0" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_RANGE_ATTACK1" |
|
|
|
" " |
|
" Interrupts" |
|
) |
|
|
|
//========================================================= |
|
// Fail Schedule |
|
//========================================================= |
|
|
|
//========================================================= |
|
// > SCHED_ASSASSIN_FAIL |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ASSASSIN_FAIL, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" |
|
" TASK_WAIT_FACE_ENEMY 2" |
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY" |
|
" " |
|
" Interrupts" |
|
" COND_LIGHT_DAMAGE" |
|
" COND_HEAVY_DAMAGE" |
|
" COND_CAN_RANGE_ATTACK1" |
|
" COND_CAN_RANGE_ATTACK2" |
|
" COND_CAN_MELEE_ATTACK1" |
|
" COND_HEAR_DANGER" |
|
" COND_HEAR_PLAYER" |
|
) |
|
|
|
|
|
|
|
|
|
//========================================================= |
|
// > SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY1 |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY1, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_WAIT 0.2" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_RANGE_ATTACK1" |
|
" 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_CAN_MELEE_ATTACK1" |
|
" COND_NEW_ENEMY" |
|
" COND_HEAR_DANGER" |
|
|
|
) |
|
|
|
//========================================================= |
|
// > SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY2 |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY2, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_WAIT 0.2" |
|
" TASK_FACE_ENEMY 0" |
|
" TASK_RANGE_ATTACK1 0" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_RANGE_ATTACK1" |
|
" 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_CAN_MELEE_ATTACK1" |
|
" COND_NEW_ENEMY" |
|
" COND_HEAR_DANGER" |
|
|
|
) |
|
|
|
|
|
|
|
//========================================================= |
|
// hide from the loudest sound source |
|
//========================================================= |
|
|
|
//========================================================= |
|
// > SCHED_ASSASSIN_TAKE_COVER_FROM_BEST_SOUND |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ASSASSIN_TAKE_COVER_FROM_BEST_SOUND, |
|
|
|
" Tasks" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_MELEE_ATTACK1" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_FIND_COVER_FROM_BEST_SOUND 0" |
|
" TASK_RUN_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" TASK_REMEMBER MEMORY:INCOVER" |
|
" TASK_TURN_LEFT 179" |
|
" " |
|
" Interrupts" |
|
" COND_NEW_ENEMY" |
|
|
|
) |
|
|
|
//========================================================= |
|
// > SCHED_ASSASSIN_HIDE |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ASSASSIN_HIDE, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" |
|
" TASK_WAIT 2.0" |
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY" |
|
|
|
" Interrupts" |
|
" COND_NEW_ENEMY" |
|
" COND_SEE_ENEMY" |
|
" COND_SEE_FEAR" |
|
" COND_LIGHT_DAMAGE" |
|
" COND_HEAVY_DAMAGE" |
|
" COND_PROVOKED" |
|
" COND_HEAR_DANGER" |
|
) |
|
|
|
//========================================================= |
|
// > SCHED_ASSASSIN_HUNT |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ASSASSIN_HUNT, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY2" |
|
" TASK_GET_PATH_TO_ENEMY 0" |
|
" TASK_RUN_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
|
|
" Interrupts" |
|
" COND_NEW_ENEMY" |
|
" COND_CAN_RANGE_ATTACK1" |
|
" COND_HEAR_DANGER" |
|
) |
|
|
|
AI_END_CUSTOM_NPC()
|
|
|