source-engine/game/server/hl1/hl1_npc_vortigaunt.cpp
2022-04-16 12:05:19 +03:00

744 lines
19 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Alien slave monster
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "beam_shared.h"
#include "game.h"
#include "ai_default.h"
#include "ai_schedule.h"
#include "ai_hull.h"
#include "ai_route.h"
#include "ai_squad.h"
#include "npcevent.h"
#include "gib.h"
//#include "AI_Interactions.h"
#include "ndebugoverlay.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "hl1_npc_vortigaunt.h"
#include "soundent.h"
#include "player.h"
#include "IEffects.h"
#include "basecombatweapon.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"
//=========================================================
// Monster's Anim Events Go Here
//=========================================================
#define ISLAVE_AE_CLAW ( 1 )
#define ISLAVE_AE_CLAWRAKE ( 2 )
#define ISLAVE_AE_ZAP_POWERUP ( 3 )
#define ISLAVE_AE_ZAP_SHOOT ( 4 )
#define ISLAVE_AE_ZAP_DONE ( 5 )
ConVar sk_islave_health( "sk_islave_health","50");
ConVar sk_islave_dmg_claw( "sk_islave_dmg_claw","8");
ConVar sk_islave_dmg_clawrake( "sk_islave_dmg_clawrake","25");
ConVar sk_islave_dmg_zap( "sk_islave_dmg_zap","15");
LINK_ENTITY_TO_CLASS( monster_alien_slave, CNPC_Vortigaunt );
BEGIN_DATADESC( CNPC_Vortigaunt )
DEFINE_FIELD( m_iBravery, FIELD_INTEGER ),
DEFINE_ARRAY( m_pBeam, FIELD_CLASSPTR, VORTIGAUNT_MAX_BEAMS ),
DEFINE_FIELD( m_iBeams, FIELD_INTEGER ),
DEFINE_FIELD( m_flNextAttack, FIELD_TIME ),
DEFINE_FIELD( m_iVoicePitch, FIELD_INTEGER ),
DEFINE_FIELD( m_hDead, FIELD_EHANDLE ),
END_DATADESC()
enum
{
SCHED_VORTIGAUNT_ATTACK = LAST_SHARED_SCHEDULE,
};
#define VORTIGAUNT_IGNORE_PLAYER 64
//=========================================================
// Spawn
//=========================================================
void CNPC_Vortigaunt::Spawn()
{
Precache( );
SetModel( "models/islave.mdl" );
SetRenderColor( 255, 255, 255, 255 );
SetHullType(HULL_HUMAN);
SetHullSizeNormal();
SetSolid( SOLID_BBOX );
AddSolidFlags( FSOLID_NOT_STANDABLE );
SetMoveType( MOVETYPE_STEP );
m_bloodColor = BLOOD_COLOR_GREEN;
ClearEffects();
m_iHealth = sk_islave_health.GetFloat();
//pev->view_ofs = VEC_VIEW;// position of the eyes relative to monster's origin.
m_flFieldOfView = VIEW_FIELD_WIDE;
m_NPCState = NPC_STATE_NONE;
m_iVoicePitch = random->RandomInt( 85, 110 );
CapabilitiesClear();
CapabilitiesAdd( bits_CAP_MOVE_GROUND );
CapabilitiesAdd( bits_CAP_SQUAD );
CapabilitiesAdd( bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP );
CapabilitiesAdd ( bits_CAP_INNATE_RANGE_ATTACK1 );
CapabilitiesAdd ( bits_CAP_INNATE_MELEE_ATTACK1 );
m_iBravery = 0;
NPCInit();
BaseClass::Spawn();
}
//=========================================================
// Precache - precaches all resources this monster needs
//=========================================================
void CNPC_Vortigaunt::Precache()
{
BaseClass::Precache();
PrecacheModel("models/islave.mdl");
PrecacheModel("sprites/lgtning.vmt");
PrecacheScriptSound( "Vortigaunt.Pain" );
PrecacheScriptSound( "Vortigaunt.Die" );
PrecacheScriptSound( "Vortigaunt.AttackHit" );
PrecacheScriptSound( "Vortigaunt.AttackMiss" );
PrecacheScriptSound( "Vortigaunt.ZapPowerup" );
PrecacheScriptSound( "Vortigaunt.ZapShoot" );
}
Disposition_t CNPC_Vortigaunt::IRelationType ( CBaseEntity *pTarget )
{
if ( (pTarget->IsPlayer()) )
{
if ( (GetSpawnFlags() & VORTIGAUNT_IGNORE_PLAYER ) && !HasMemory( bits_MEMORY_PROVOKED ) )
return D_NU;
}
return BaseClass::IRelationType( pTarget );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
Class_T CNPC_Vortigaunt::Classify ( void )
{
return CLASS_ALIEN_MILITARY;
}
void CNPC_Vortigaunt::CallForHelp( char *szClassname, float flDist, CBaseEntity * pEnemy, Vector &vecLocation )
{
// ALERT( at_aiconsole, "help " );
// skip ones not on my netname
if ( !m_pSquad )
return;
AISquadIter_t iter;
for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
{
float d = ( GetAbsOrigin() - pSquadMember->GetAbsOrigin() ).Length();
if ( d < flDist )
{
pSquadMember->Remember( bits_MEMORY_PROVOKED );
pSquadMember->UpdateEnemyMemory( pEnemy, vecLocation );
}
}
}
//=========================================================
// ALertSound - scream
//=========================================================
void CNPC_Vortigaunt::AlertSound( void )
{
if ( GetEnemy() != NULL )
{
SENTENCEG_PlayRndSz( edict(), "SLV_ALERT", 0.85, SNDLVL_NORM, 0, m_iVoicePitch );
Vector vecTmp = GetEnemy()->GetAbsOrigin();
CallForHelp( "monster_alien_slave", 512, GetEnemy(), vecTmp );
}
}
//=========================================================
// IdleSound
//=========================================================
void CNPC_Vortigaunt::IdleSound( void )
{
if ( random->RandomInt( 0, 2 ) == 0)
SENTENCEG_PlayRndSz( edict(), "SLV_IDLE", 0.85, SNDLVL_NORM, 0, m_iVoicePitch);
}
//=========================================================
// PainSound
//=========================================================
void CNPC_Vortigaunt::PainSound( const CTakeDamageInfo &info )
{
if ( random->RandomInt( 0, 2 ) == 0)
{
CPASAttenuationFilter filter( this );
CSoundParameters params;
if ( GetParametersForSound( "Vortigaunt.Pain", params, NULL ) )
{
EmitSound_t ep( params );
params.pitch = m_iVoicePitch;
EmitSound( filter, entindex(), ep );
}
}
}
//=========================================================
// DieSound
//=========================================================
void CNPC_Vortigaunt::DeathSound( const CTakeDamageInfo &info )
{
CPASAttenuationFilter filter( this );
CSoundParameters params;
if ( GetParametersForSound( "Vortigaunt.Die", params, NULL ) )
{
EmitSound_t ep( params );
params.pitch = m_iVoicePitch;
EmitSound( filter, entindex(), ep );
}
}
int CNPC_Vortigaunt::GetSoundInterests ( void )
{
return SOUND_WORLD |
SOUND_COMBAT |
SOUND_DANGER |
SOUND_PLAYER;
}
void CNPC_Vortigaunt::Event_Killed( const CTakeDamageInfo &info )
{
ClearBeams( );
BaseClass::Event_Killed( info );
}
//=========================================================
// SetYawSpeed - allows each sequence to have a different
// turn rate associated with it.
//=========================================================
float CNPC_Vortigaunt::MaxYawSpeed ( void )
{
float flYS;
switch ( GetActivity() )
{
case ACT_WALK:
flYS = 50;
break;
case ACT_RUN:
flYS = 70;
break;
case ACT_IDLE:
flYS = 50;
break;
default:
flYS = 90;
break;
}
return flYS;
}
//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//
// Returns number of events handled, 0 if none.
//=========================================================
void CNPC_Vortigaunt::HandleAnimEvent( animevent_t *pEvent )
{
// ALERT( at_console, "event %d : %f\n", pEvent->event, pev->frame );
switch( pEvent->event )
{
case ISLAVE_AE_CLAW:
{
// SOUND HERE!
CBaseEntity *pHurt = CheckTraceHullAttack( 40, Vector(-10,-10,-10), Vector(10,10,10), sk_islave_dmg_claw.GetFloat(), DMG_SLASH );
CPASAttenuationFilter filter( this );
if ( pHurt )
{
if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) )
pHurt->ViewPunch( QAngle( 5, 0, -18 ) );
// Play a random attack hit sound
CSoundParameters params;
if ( GetParametersForSound( "Vortigaunt.AttackHit", params, NULL ) )
{
EmitSound_t ep( params );
params.pitch = m_iVoicePitch;
EmitSound( filter, entindex(), ep );
}
}
else
{
// Play a random attack miss sound
CSoundParameters params;
if ( GetParametersForSound( "Vortigaunt.AttackMiss", params, NULL ) )
{
EmitSound_t ep( params );
params.pitch = m_iVoicePitch;
EmitSound( filter, entindex(), ep );
}
}
}
break;
case ISLAVE_AE_CLAWRAKE:
{
CBaseEntity *pHurt = CheckTraceHullAttack( 40, Vector(-10,-10,-10), Vector(10,10,10), sk_islave_dmg_clawrake.GetFloat(), DMG_SLASH );
CPASAttenuationFilter filter2( this );
if ( pHurt )
{
if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) )
pHurt->ViewPunch( QAngle( 5, 0, 18 ) );
CSoundParameters params;
if ( GetParametersForSound( "Vortigaunt.AttackHit", params, NULL ) )
{
EmitSound_t ep( params );
params.pitch = m_iVoicePitch;
EmitSound( filter2, entindex(), ep );
}
}
else
{
CSoundParameters params;
if ( GetParametersForSound( "Vortigaunt.AttackMiss", params, NULL ) )
{
EmitSound_t ep( params );
params.pitch = m_iVoicePitch;
EmitSound( filter2, entindex(), ep );
}
}
}
break;
case ISLAVE_AE_ZAP_POWERUP:
{
// speed up attack when on hard
if ( g_iSkillLevel == SKILL_HARD )
m_flPlaybackRate = 1.5;
Vector v_forward;
GetVectors( &v_forward, NULL, NULL );
CBroadcastRecipientFilter filter;
te->DynamicLight( filter, 0.0, &GetAbsOrigin(), 125, 200, 100, 2, 120, 0.2 / m_flPlaybackRate, 0 );
if ( m_hDead != NULL )
{
WackBeam( -1, m_hDead );
WackBeam( 1, m_hDead );
}
else
{
ArmBeam( -1 );
ArmBeam( 1 );
BeamGlow( );
}
CPASAttenuationFilter filter3( this );
CSoundParameters params;
if ( GetParametersForSound( "Vortigaunt.ZapPowerup", params, NULL ) )
{
EmitSound_t ep( params );
ep.m_nPitch = 100 + m_iBeams * 10;
EmitSound( filter3, entindex(), ep );
}
// Huh? Model doesn't have multiple texturegroups, commented this out. -LH
// m_nSkin = m_iBeams / 2;
}
break;
case ISLAVE_AE_ZAP_SHOOT:
{
ClearBeams( );
if ( m_hDead != NULL )
{
Vector vecDest = m_hDead->GetAbsOrigin() + Vector( 0, 0, 38 );
trace_t trace;
UTIL_TraceHull( vecDest, vecDest, GetHullMins(), GetHullMaxs(),MASK_SOLID, m_hDead, COLLISION_GROUP_NONE, &trace );
if ( !trace.startsolid )
{
CBaseEntity *pNew = Create( "monster_alien_slave", m_hDead->GetAbsOrigin(), m_hDead->GetAbsAngles() );
pNew->AddSpawnFlags( 1 );
WackBeam( -1, pNew );
WackBeam( 1, pNew );
UTIL_Remove( m_hDead );
break;
}
}
ClearMultiDamage();
ZapBeam( -1 );
ZapBeam( 1 );
CPASAttenuationFilter filter4( this );
EmitSound( filter4, entindex(), "Vortigaunt.ZapShoot" );
ApplyMultiDamage();
m_flNextAttack = gpGlobals->curtime + random->RandomFloat( 0.5, 4.0 );
}
break;
case ISLAVE_AE_ZAP_DONE:
{
ClearBeams();
}
break;
default:
BaseClass::HandleAnimEvent( pEvent );
break;
}
}
//------------------------------------------------------------------------------
// Purpose : For innate range attack
// Input :
// Output :
//------------------------------------------------------------------------------
int CNPC_Vortigaunt::RangeAttack1Conditions( float flDot, float flDist )
{
if ( GetEnemy() == NULL )
return( COND_LOST_ENEMY );
if ( gpGlobals->curtime < m_flNextAttack )
return COND_NONE;
if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
return COND_NONE;
return COND_CAN_RANGE_ATTACK1;
}
void CNPC_Vortigaunt::StartTask( const Task_t *pTask )
{
ClearBeams();
BaseClass::StartTask( pTask );
}
//=========================================================
// TakeDamage - get provoked when injured
//=========================================================
int CNPC_Vortigaunt::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
{
// don't slash one of your own
if ( ( inputInfo.GetDamageType() & DMG_SLASH ) && inputInfo.GetAttacker() && IRelationType( inputInfo.GetAttacker() ) == D_NU )
return 0;
Remember( bits_MEMORY_PROVOKED );
return BaseClass::OnTakeDamage_Alive( inputInfo );
}
void CNPC_Vortigaunt::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
{
if ( info.GetDamageType() & DMG_SHOCK )
return;
BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
}
//=========================================================
//=========================================================
int CNPC_Vortigaunt::SelectSchedule( void )
{
ClearBeams();
switch ( m_NPCState )
{
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();
}
if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
return SCHED_RANGE_ATTACK1;
if ( m_iHealth < 20 || m_iBravery < 0)
{
if ( !HasCondition( COND_CAN_MELEE_ATTACK1 ) )
{
SetDefaultFailSchedule( SCHED_CHASE_ENEMY );
if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
return SCHED_TAKE_COVER_FROM_ENEMY;
if ( HasCondition ( COND_SEE_ENEMY ) && HasCondition ( COND_ENEMY_FACING_ME ) )
return SCHED_TAKE_COVER_FROM_ENEMY;
}
}
break;
}
return BaseClass::SelectSchedule( );
}
int CNPC_Vortigaunt::TranslateSchedule( int scheduleType )
{
//Oops can't get to my enemy.
if ( scheduleType == SCHED_CHASE_ENEMY_FAILED )
{
return SCHED_ESTABLISH_LINE_OF_FIRE;
}
switch ( scheduleType )
{
case SCHED_FAIL:
if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
{
return ( SCHED_MELEE_ATTACK1 );
}
break;
case SCHED_RANGE_ATTACK1:
{
//Adrian - HACK HACK! This should've been done up there ^^^^
if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
{
return ( SCHED_MELEE_ATTACK1 );
}
return SCHED_VORTIGAUNT_ATTACK;
}
break;
}
return BaseClass::TranslateSchedule( scheduleType );
}
//=========================================================
// ArmBeam - small beam from arm to nearby geometry
//=========================================================
void CNPC_Vortigaunt::ArmBeam( int side )
{
trace_t tr;
float flDist = 1.0;
if ( m_iBeams >= VORTIGAUNT_MAX_BEAMS )
return;
Vector forward, right, up;
Vector vecAim;
AngleVectors( GetAbsAngles(), &forward, &right, &up );
Vector vecSrc = GetAbsOrigin() + up * 36 + right * side * 16 + forward * 32;
for (int i = 0; i < 3; i++)
{
vecAim = right * side * random->RandomFloat( 0, 1 ) + up * random->RandomFloat( -1, 1 );
trace_t tr1;
UTIL_TraceLine ( vecSrc, vecSrc + vecAim * 512, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr1);
if (flDist > tr1.fraction)
{
tr = tr1;
flDist = tr.fraction;
}
}
// Couldn't find anything close enough
if ( flDist == 1.0 )
return;
if( tr.m_pEnt && tr.m_pEnt->m_takedamage && !tr.m_pEnt->IsNPC() )
{
CTakeDamageInfo info( this, this, 10, DMG_SHOCK );
CalculateMeleeDamageForce( &info, vecAim, tr.endpos );
tr.m_pEnt->TakeDamage( info );
}
UTIL_DecalTrace( &tr, "FadingScorch" );
m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/lgtning.vmt", 3.0f );
if ( m_pBeam[m_iBeams] == NULL )
return;
m_pBeam[m_iBeams]->PointEntInit( tr.endpos, this );
m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 );
m_pBeam[m_iBeams]->SetColor( 96, 128, 16 );
m_pBeam[m_iBeams]->SetBrightness( 64 );
m_pBeam[m_iBeams]->SetNoise( 12.8 );
m_pBeam[m_iBeams]->AddSpawnFlags( SF_BEAM_TEMPORARY );
m_iBeams++;
}
//=========================================================
// BeamGlow - brighten all beams
//=========================================================
void CNPC_Vortigaunt::BeamGlow( )
{
int b = m_iBeams * 32;
if ( b > 255 )
b = 255;
for ( int i = 0; i < m_iBeams; i++ )
{
if ( m_pBeam[i] != NULL )
{
if ( m_pBeam[i]->GetBrightness() != 255 )
m_pBeam[i]->SetBrightness( b );
}
}
}
//=========================================================
// WackBeam - regenerate dead colleagues
//=========================================================
void CNPC_Vortigaunt::WackBeam( int side, CBaseEntity *pEntity )
{
Vector vecDest;
if ( m_iBeams >= VORTIGAUNT_MAX_BEAMS )
return;
if ( pEntity == NULL )
return;
m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/lgtning.vmt", 3.0f );
if ( m_pBeam[m_iBeams] == NULL )
return;
m_pBeam[m_iBeams]->PointEntInit( pEntity->WorldSpaceCenter(), this );
m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 );
m_pBeam[m_iBeams]->SetColor( 180, 255, 96 );
m_pBeam[m_iBeams]->SetBrightness( 255 );
m_pBeam[m_iBeams]->SetNoise( 12.8 );
m_pBeam[m_iBeams]->AddSpawnFlags( SF_BEAM_TEMPORARY );
m_iBeams++;
}
//=========================================================
// ZapBeam - heavy damage directly forward
//=========================================================
void CNPC_Vortigaunt::ZapBeam( int side )
{
Vector vecSrc, vecAim;
trace_t tr;
CBaseEntity *pEntity;
if ( m_iBeams >= VORTIGAUNT_MAX_BEAMS )
return;
Vector forward, right, up;
AngleVectors( GetAbsAngles(), &forward, &right, &up );
vecSrc = GetAbsOrigin() + up * 36;
vecAim = GetShootEnemyDir( vecSrc );
float deflection = 0.01;
vecAim = vecAim + side * right * random->RandomFloat( 0, deflection ) + up * random->RandomFloat( -deflection, deflection );
UTIL_TraceLine ( vecSrc, vecSrc + vecAim * 1024, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/lgtning.vmt", 5.0f );
if ( m_pBeam[m_iBeams] == NULL )
return;
m_pBeam[m_iBeams]->PointEntInit( tr.endpos, this );
m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 );
m_pBeam[m_iBeams]->SetColor( 180, 255, 96 );
m_pBeam[m_iBeams]->SetBrightness( 255 );
m_pBeam[m_iBeams]->SetNoise( 3.2f );
m_pBeam[m_iBeams]->AddSpawnFlags( SF_BEAM_TEMPORARY );
m_iBeams++;
pEntity = tr.m_pEnt;
if ( pEntity != NULL && m_takedamage )
{
CTakeDamageInfo info( this, this, sk_islave_dmg_zap.GetFloat(), DMG_SHOCK );
CalculateMeleeDamageForce( &info, vecAim, tr.endpos );
pEntity->DispatchTraceAttack( info, vecAim, &tr );
}
}
//=========================================================
// ClearBeams - remove all beams
//=========================================================
void CNPC_Vortigaunt::ClearBeams( )
{
for (int i = 0; i < VORTIGAUNT_MAX_BEAMS; i++)
{
if (m_pBeam[i])
{
UTIL_Remove( m_pBeam[i] );
m_pBeam[i] = NULL;
}
}
m_iBeams = 0;
m_nSkin = 0;
}
//------------------------------------------------------------------------------
//
// Schedules
//
//------------------------------------------------------------------------------
AI_BEGIN_CUSTOM_NPC( monster_alien_slave, CNPC_Vortigaunt )
//=========================================================
// > SCHED_VORTIGAUNT_ATTACK
//=========================================================
DEFINE_SCHEDULE
(
SCHED_VORTIGAUNT_ATTACK,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_FACE_IDEAL 0"
" TASK_RANGE_ATTACK1 0"
" "
" Interrupts"
" COND_CAN_MELEE_ATTACK1"
" COND_HEAVY_DAMAGE"
" COND_HEAR_DANGER"
)
AI_END_CUSTOM_NPC()