//========= 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()