📄 hl1_npc_hassassin.cpp
字号:
#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_Route.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_iShell;
int m_iAmmoType;
public:
DECLARE_DATADESC();
DEFINE_CUSTOM_AI;
};
LINK_ENTITY_TO_CLASS( monster_human_assassin, CNPC_HAssassin );
BEGIN_DATADESC( CNPC_HAssassin )
DEFINE_FIELD( CNPC_HAssassin, m_flLastShot, FIELD_TIME ),
DEFINE_FIELD( CNPC_HAssassin, m_flDiviation, FIELD_FLOAT ),
DEFINE_FIELD( CNPC_HAssassin, m_flNextJump, FIELD_TIME ),
DEFINE_FIELD( CNPC_HAssassin, m_vecJumpVelocity, FIELD_VECTOR ),
DEFINE_FIELD( CNPC_HAssassin, m_flNextGrenadeCheck, FIELD_TIME ),
DEFINE_FIELD( CNPC_HAssassin, m_vecTossVelocity, FIELD_VECTOR ),
DEFINE_FIELD( CNPC_HAssassin, m_fThrowGrenade, FIELD_BOOLEAN ),
DEFINE_FIELD( CNPC_HAssassin, m_iTargetRanderamt, FIELD_INTEGER ),
DEFINE_FIELD( CNPC_HAssassin, m_iFrustration, 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;
m_fEffects = 0;
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");
engine->PrecacheModel("models/hassassin.mdl");
enginesound->PrecacheSound("weapons/pl_gun1.wav");
enginesound->PrecacheSound("weapons/pl_gun2.wav");
enginesound->PrecacheSound("debris/beamstart1.wav");
UTIL_PrecacheOther( "npc_handgrenade" );
enginesound->PrecacheSound("player/pl_step1.wav");
enginesound->PrecacheSound("player/pl_step2.wav");
enginesound->PrecacheSound("player/pl_step3.wav");
enginesound->PrecacheSound("player/pl_step4.wav");
// m_iShell = PRECACHE_MODEL ("models/shell.mdl");// brass shell
}
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 = sv_gravity.GetFloat();
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:
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 );
m_flCycle = 0;
}
else
{
SetActivity( ACT_ASSASSIN_FLY_DOWN );
m_flCycle = 0;
}
ResetSequenceInfo( );
}
if ( GetFlags() & FL_ONGROUND)
{
// ALERT( at_console, "on ground\n");
TaskComplete( );
}
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" );
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -