📄 hl1_npc_hgrunt.cpp
字号:
//=========== (C) Copyright 2000 Valve, L.L.C. All rights reserved. ===========
//
// The copyright to the contents herein is the property of Valve, L.L.C.
// The contents may be used and/or copied only with the written permission of
// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
// the agreement/contract under which the contents have been supplied.
//
// Purpose: Bullseyes act as targets for other NPC's to attack and to trigger
// events
//
// $Workfile: $
// $Date: $
//
//-----------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================
#include "cbase.h"
#include "beam_shared.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 "AI_Squad.h"
#include "AI_SquadSlot.h"
#include "AI_Motor.h"
#include "hl1_npc_hgrunt.h"
#include "soundent.h"
#include "game.h"
#include "NPCEvent.h"
#include "EntityList.h"
#include "activitylist.h"
#include "animation.h"
#include "engine/IEngineSound.h"
#include "ammodef.h"
#include "basecombatweapon.h"
#include "hl1_basegrenade.h"
#include "customentity.h"
#include "ai_interactions.h"
#include "scripted.h"
#include "hl1_basegrenade.h"
#include "hl1_grenade_mp5.h"
ConVar sk_hgrunt_health( "sk_hgrunt_health","0");
ConVar sk_hgrunt_kick ( "sk_hgrunt_kick", "0" );
ConVar sk_hgrunt_pellets ( "sk_hgrunt_pellets", "0" );
ConVar sk_hgrunt_gspeed ( "sk_hgrunt_gspeed", "0" );
extern ConVar sk_plr_dmg_grenade;
extern ConVar sk_plr_dmg_mp5_grenade;
#define SF_GRUNT_LEADER ( 1 << 5 )
int g_fGruntQuestion; // true if an idle grunt asked a question. Cleared when someone answers.
int g_iSquadIndex = 0;
#define HGRUNT_GUN_SPREAD 0.08716f
//=========================================================
// monster-specific DEFINE's
//=========================================================
#define GRUNT_CLIP_SIZE 36 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x!
#define GRUNT_VOL 0.35 // volume of grunt sounds
#define GRUNT_SNDLVL SNDLVL_NORM // soundlevel of grunt sentences
#define HGRUNT_LIMP_HEALTH 20
#define HGRUNT_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a grunt with a single headshot.
#define HGRUNT_NUM_HEADS 2 // how many grunt heads are there?
#define HGRUNT_MINIMUM_HEADSHOT_DAMAGE 15 // must do at least this much damage in one shot to head to score a headshot kill
#define HGRUNT_SENTENCE_VOLUME (float)0.35 // volume of grunt sentences
#define HGRUNT_9MMAR ( 1 << 0)
#define HGRUNT_HANDGRENADE ( 1 << 1)
#define HGRUNT_GRENADELAUNCHER ( 1 << 2)
#define HGRUNT_SHOTGUN ( 1 << 3)
#define HEAD_GROUP 1
#define HEAD_GRUNT 0
#define HEAD_COMMANDER 1
#define HEAD_SHOTGUN 2
#define HEAD_M203 3
#define GUN_GROUP 2
#define GUN_MP5 0
#define GUN_SHOTGUN 1
#define GUN_NONE 2
//=========================================================
// Monster's Anim Events Go Here
//=========================================================
#define HGRUNT_AE_RELOAD ( 2 )
#define HGRUNT_AE_KICK ( 3 )
#define HGRUNT_AE_BURST1 ( 4 )
#define HGRUNT_AE_BURST2 ( 5 )
#define HGRUNT_AE_BURST3 ( 6 )
#define HGRUNT_AE_GREN_TOSS ( 7 )
#define HGRUNT_AE_GREN_LAUNCH ( 8 )
#define HGRUNT_AE_GREN_DROP ( 9 )
#define HGRUNT_AE_CAUGHT_ENEMY ( 10) // grunt established sight with an enemy (player only) that had previously eluded the squad.
#define HGRUNT_AE_DROP_GUN ( 11) // grunt (probably dead) is dropping his mp5.
const char *CNPC_HGrunt::pGruntSentences[] =
{
"HG_GREN", // grenade scared grunt
"HG_ALERT", // sees player
"HG_MONSTER", // sees monster
"HG_COVER", // running to cover
"HG_THROW", // about to throw grenade
"HG_CHARGE", // running out to get the enemy
"HG_TAUNT", // say rude things
};
enum
{
HGRUNT_SENT_NONE = -1,
HGRUNT_SENT_GREN = 0,
HGRUNT_SENT_ALERT,
HGRUNT_SENT_MONSTER,
HGRUNT_SENT_COVER,
HGRUNT_SENT_THROW,
HGRUNT_SENT_CHARGE,
HGRUNT_SENT_TAUNT,
} HGRUNT_SENTENCE_TYPES;
LINK_ENTITY_TO_CLASS( monster_human_grunt, CNPC_HGrunt );
//=========================================================
// monster-specific schedule types
//=========================================================
enum
{
SCHED_GRUNT_FAIL = LAST_SHARED_SCHEDULE,
SCHED_GRUNT_COMBAT_FAIL,
SCHED_GRUNT_VICTORY_DANCE,
SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE,
SCHED_GRUNT_FOUND_ENEMY,
SCHED_GRUNT_COMBAT_FACE,
SCHED_GRUNT_SIGNAL_SUPPRESS,
SCHED_GRUNT_SUPPRESS,
SCHED_GRUNT_WAIT_IN_COVER,
SCHED_GRUNT_TAKE_COVER,
SCHED_GRUNT_GRENADE_COVER,
SCHED_GRUNT_TOSS_GRENADE_COVER,
SCHED_GRUNT_HIDE_RELOAD,
SCHED_GRUNT_SWEEP,
SCHED_GRUNT_RANGE_ATTACK1A,
SCHED_GRUNT_RANGE_ATTACK1B,
SCHED_GRUNT_RANGE_ATTACK2,
SCHED_GRUNT_REPEL,
SCHED_GRUNT_REPEL_ATTACK,
SCHED_GRUNT_REPEL_LAND,
SCHED_GRUNT_TAKE_COVER_FAILED,
SCHED_GRUNT_RELOAD,
SCHED_GRUNT_TAKE_COVER_FROM_ENEMY,
SCHED_GRUNT_BARNACLE_HIT,
SCHED_GRUNT_BARNACLE_PULL,
SCHED_GRUNT_BARNACLE_CHOMP,
SCHED_GRUNT_BARNACLE_CHEW,
};
//=========================================================
// monster-specific tasks
//=========================================================
enum
{
TASK_GRUNT_FACE_TOSS_DIR = LAST_SHARED_TASK + 1,
TASK_GRUNT_SPEAK_SENTENCE,
TASK_GRUNT_CHECK_FIRE,
};
//=========================================================
// monster-specific conditions
//=========================================================
enum
{
COND_GRUNT_NOFIRE = LAST_SHARED_CONDITION + 1,
};
// -----------------------------------------------
// > Squad slots
// -----------------------------------------------
enum SquadSlot_T
{
SQUAD_SLOT_GRENADE1 = LAST_SHARED_SQUADSLOT,
SQUAD_SLOT_GRENADE2,
SQUAD_SLOT_ENGAGE1,
SQUAD_SLOT_ENGAGE2,
};
int ACT_GRUNT_LAUNCH_GRENADE;
int ACT_GRUNT_TOSS_GRENADE;
int ACT_GRUNT_MP5_STANDING;
int ACT_GRUNT_MP5_CROUCHING;
int ACT_GRUNT_SHOTGUN_STANDING;
int ACT_GRUNT_SHOTGUN_CROUCHING;
//---------------------------------------------------------
// Save/Restore
//---------------------------------------------------------
BEGIN_DATADESC( CNPC_HGrunt )
DEFINE_FIELD( CNPC_HGrunt, m_flNextGrenadeCheck, FIELD_TIME ),
DEFINE_FIELD( CNPC_HGrunt, m_flNextPainTime, FIELD_TIME ),
DEFINE_FIELD( CNPC_HGrunt, m_flCheckAttackTime, FIELD_FLOAT ),
DEFINE_FIELD( CNPC_HGrunt, m_vecTossVelocity, FIELD_VECTOR ),
DEFINE_FIELD( CNPC_HGrunt, m_iLastGrenadeCondition, FIELD_INTEGER ),
DEFINE_FIELD( CNPC_HGrunt, m_fStanding, FIELD_BOOLEAN ),
DEFINE_FIELD( CNPC_HGrunt, m_fFirstEncounter, FIELD_BOOLEAN ),
DEFINE_FIELD( CNPC_HGrunt, m_iClipSize, FIELD_INTEGER ),
DEFINE_FIELD( CNPC_HGrunt, m_voicePitch, FIELD_INTEGER ),
DEFINE_FIELD( CNPC_HGrunt, m_iSentence, FIELD_INTEGER ),
DEFINE_KEYFIELD( CNPC_HGrunt, m_iWeapons, FIELD_INTEGER, "weapons" ),
DEFINE_KEYFIELD( CNPC_HGrunt, m_SquadName, FIELD_STRING, "netname" ),
END_DATADESC()
//=========================================================
// Spawn
//=========================================================
void CNPC_HGrunt::Spawn()
{
Precache( );
SetModel( "models/hgrunt.mdl" );
SetHullType(HULL_HUMAN);
SetHullSizeNormal();
SetSolid( SOLID_BBOX );
AddSolidFlags( FSOLID_NOT_STANDABLE );
SetMoveType( MOVETYPE_STEP );
m_bloodColor = BLOOD_COLOR_RED;
m_fEffects = 0;
m_iHealth = sk_hgrunt_health.GetFloat();
m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result )
m_NPCState = NPC_STATE_NONE;
m_flNextGrenadeCheck = gpGlobals->curtime + 1;
m_flNextPainTime = gpGlobals->curtime;
m_iSentence = HGRUNT_SENT_NONE;
CapabilitiesClear();
CapabilitiesAdd ( bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP | bits_CAP_MOVE_GROUND );
CapabilitiesAdd(bits_CAP_INNATE_RANGE_ATTACK1 );
// Innate range attack for grenade
CapabilitiesAdd(bits_CAP_INNATE_RANGE_ATTACK2 );
// Innate range attack for kicking
CapabilitiesAdd(bits_CAP_INNATE_MELEE_ATTACK1 );
m_fFirstEncounter = true;// this is true when the grunt spawns, because he hasn't encountered an enemy yet.
m_HackedGunPos = Vector ( 0, 0, 55 );
if ( m_iWeapons == 0)
{
// initialize to original values
m_iWeapons = HGRUNT_9MMAR | HGRUNT_HANDGRENADE;
// pev->weapons = HGRUNT_SHOTGUN;
// pev->weapons = HGRUNT_9MMAR | HGRUNT_GRENADELAUNCHER;
}
if (FBitSet( m_iWeapons, HGRUNT_SHOTGUN ))
{
SetBodygroup( GUN_GROUP, GUN_SHOTGUN );
m_iClipSize = 8;
}
else
{
m_iClipSize = GRUNT_CLIP_SIZE;
}
m_cAmmoLoaded = m_iClipSize;
if ( random->RandomInt( 0, 99 ) < 80)
m_nSkin = 0; // light skin
else
m_nSkin = 1; // dark skin
if (FBitSet( m_iWeapons, HGRUNT_SHOTGUN ))
{
SetBodygroup( HEAD_GROUP, HEAD_SHOTGUN);
}
else if (FBitSet( m_iWeapons, HGRUNT_GRENADELAUNCHER ))
{
SetBodygroup( HEAD_GROUP, HEAD_M203 );
m_nSkin = 1; // alway dark skin
}
m_flTalkWaitTime = 0;
//HACK
g_iSquadIndex = 0;
BaseClass::Spawn();
NPCInit();
}
int CNPC_HGrunt::IRelationPriority( CBaseEntity *pTarget )
{
//I hate alien grunts more than anything.
if ( pTarget->Classify() == CLASS_ALIEN_MILITARY )
{
if ( FClassnameIs( pTarget, "monster_alien_grunt" ) )
{
return ( BaseClass::IRelationPriority ( pTarget ) + 1 );
}
}
return BaseClass::IRelationPriority( pTarget );
}
//=========================================================
// Precache - precaches all resources this monster needs
//=========================================================
void CNPC_HGrunt::Precache()
{
m_iAmmoType = GetAmmoDef()->Index("9mmRound");
engine->PrecacheModel("models/hgrunt.mdl");
enginesound->PrecacheSound( "hgrunt/gr_mgun1.wav" );
enginesound->PrecacheSound( "hgrunt/gr_mgun2.wav" );
enginesound->PrecacheSound( "hgrunt/gr_die1.wav" );
enginesound->PrecacheSound( "hgrunt/gr_die2.wav" );
enginesound->PrecacheSound( "hgrunt/gr_die3.wav" );
enginesound->PrecacheSound( "hgrunt/gr_pain1.wav" );
enginesound->PrecacheSound( "hgrunt/gr_pain2.wav" );
enginesound->PrecacheSound( "hgrunt/gr_pain3.wav" );
enginesound->PrecacheSound( "hgrunt/gr_pain4.wav" );
enginesound->PrecacheSound( "hgrunt/gr_pain5.wav" );
enginesound->PrecacheSound( "hgrunt/gr_reload1.wav" );
enginesound->PrecacheSound( "weapons/glauncher.wav" );
enginesound->PrecacheSound( "weapons/sbarrel1.wav" );
enginesound->PrecacheSound("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event
// get voice pitch
if ( random->RandomInt(0,1))
m_voicePitch = 109 + random->RandomInt(0,7);
else
m_voicePitch = 100;
m_iBrassShell = engine->PrecacheModel ("models/shell.mdl");// brass shell
m_iShotgunShell = engine->PrecacheModel ("models/shotgunshell.mdl");
BaseClass::Precache();
UTIL_PrecacheOther( "grenade_hand" );
UTIL_PrecacheOther( "grenade_mp5" );
}
//=========================================================
// someone else is talking - don't speak
//=========================================================
bool CNPC_HGrunt::FOkToSpeak( void )
{
// if someone else is talking, don't speak
if ( gpGlobals->curtime <= m_flTalkWaitTime )
return FALSE;
if ( m_spawnflags & SF_NPC_GAG )
{
if ( m_NPCState != NPC_STATE_COMBAT )
{
// no talking outside of combat if gagged.
return FALSE;
}
}
return TRUE;
}
//=========================================================
// Speak Sentence - say your cued up sentence.
//
// Some grunt sentences (take cover and charge) rely on actually
// being able to execute the intended action. It's really lame
// when a grunt says 'COVER ME' and then doesn't move. The problem
// is that the sentences were played when the decision to TRY
// to move to cover was made. Now the sentence is played after
// we know for sure that there is a valid path. The schedule
// may still fail but in most cases, well after the grunt has
// started moving.
//=========================================================
void CNPC_HGrunt::SpeakSentence( void )
{
if ( m_iSentence == HGRUNT_SENT_NONE )
{
// no sentence cued up.
return;
}
if ( FOkToSpeak() )
{
SENTENCEG_PlayRndSz( edict(), pGruntSentences[ m_iSentence ], HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
JustSpoke();
}
}
//=========================================================
//=========================================================
void CNPC_HGrunt::JustSpoke( void )
{
m_flTalkWaitTime = gpGlobals->curtime + random->RandomFloat( 1.5f, 2.0f );
m_iSentence = HGRUNT_SENT_NONE;
}
//=========================================================
// PrescheduleThink - this function runs after conditions
// are collected and before scheduling code is run.
//=========================================================
void CNPC_HGrunt::PrescheduleThink ( void )
{
BaseClass::PrescheduleThink();
if ( m_pSquad && GetEnemy() != NULL )
{
if ( m_pSquad->GetLeader() == NULL )
return;
CNPC_HGrunt *pSquadLeader = (CNPC_HGrunt*)m_pSquad->GetLeader()->MyNPCPointer();
if ( pSquadLeader == NULL )
return; //Paranoid, so making sure it's ok.
if ( HasCondition ( COND_SEE_ENEMY ) )
{
// update the squad's last enemy sighting time.
pSquadLeader->m_flLastEnemySightTime = gpGlobals->curtime;
}
else
{
if ( gpGlobals->curtime - pSquadLeader->m_flLastEnemySightTime > 5 )
{
// been a while since we've seen the enemy
pSquadLeader->GetEnemies()->MarkAsEluded( GetEnemy() );
}
}
}
}
Class_T CNPC_HGrunt::Classify ( void )
{
return CLASS_HUMAN_MILITARY;
}
//=========================================================
//
// SquadRecruit(), get some monsters of my classification and
// link them as a group. returns the group size
//
//=========================================================
int CNPC_HGrunt::SquadRecruit( int searchRadius, int maxMembers )
{
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -