//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "beam_shared.h" #include "game.h" // For skill levels #include "globalstate.h" #include "npc_talker.h" #include "ai_motor.h" #include "ai_schedule.h" #include "scripted.h" #include "basecombatweapon.h" #include "soundent.h" #include "npcevent.h" #include "ai_hull.h" #include "animation.h" #include "ammodef.h" // For DMG_CLUB #include "Sprite.h" #include "npc_vortigaunt.h" #include "activitylist.h" #include "player.h" #include "items.h" #include "basegrenade_shared.h" #include "ai_interactions.h" #include "IEffects.h" #include "vstdlib/random.h" #include "engine/IEngineSound.h" #include "globals.h" #include "effect_dispatch_data.h" #include "te_effect_dispatch.h" #include "SoundEmitterSystem/isoundemittersystembase.h" #include "physics_prop_ragdoll.h" #include "RagdollBoogie.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define VORTIGAUNT_LIMP_HEALTH 20 #define VORTIGAUNT_SENTENCE_VOLUME (float)0.35 // volume of vortigaunt sentences #define VORTIGAUNT_VOL 0.35 // volume of vortigaunt sounds #define VORTIGAUNT_ATTN ATTN_NORM // attenutation of vortigaunt sentences #define VORTIGAUNT_HEAL_RECHARGE 15.0 // How long to rest between heals #define VORTIGAUNT_ZAP_GLOWGROW_TIME 0.5 // How long does glow last #define VORTIGAUNT_HEAL_GLOWGROW_TIME 1.4 // How long does glow last #define VORTIGAUNT_GLOWFADE_TIME 0.5 // How long does it fade #define VORTIGAUNT_STOMP_DIST 40 #define VORTIGAUNT_BEAM_HURT 0 #define VORTIGAUNT_BEAM_HEAL 1 #define VORTIGAUNT_STOMP_TURN_OFFSET 20 #define VORTIGAUNT_LEFT_CLAW "leftclaw" #define VORTIGAUNT_RIGHT_CLAW "rightclaw" #define VORT_CURE "VORT_CURE" #define VORT_CURESTOP "VORT_CURESTOP" #define VORT_CURE_INTERRUPT "VORT_CURE_INTERRUPT" #define VORT_ATTACK "VORT_ATTACK" #define VORT_MAD "VORT_MAD" #define VORT_SHOT "VORT_SHOT" #define VORT_PAIN "VORT_PAIN" #define VORT_DIE "VORT_DIE" #define VORT_KILL "VORT_KILL" #define VORT_LINE_FIRE "VORT_LINE_FIRE" #define VORT_POK "VORT_POK" #define VORT_EXTRACT_START "VORT_EXTRACT_START" #define VORT_EXTRACT_FINISH "VORT_EXTRACT_FINISH" // Target must be within this range to heal #define HEAL_RANGE 256 ConVar sk_vortigaunt_health( "sk_vortigaunt_health","0"); ConVar sk_vortigaunt_armor_charge( "sk_vortigaunt_armor_charge","30"); ConVar sk_vortigaunt_dmg_claw( "sk_vortigaunt_dmg_claw","0"); ConVar sk_vortigaunt_dmg_rake( "sk_vortigaunt_dmg_rake","0"); ConVar sk_vortigaunt_dmg_zap( "sk_vortigaunt_dmg_zap","0"); //========================================================= // Vortigaunt activities //========================================================= int ACT_VORTIGAUNT_AIM; int ACT_VORTIGAUNT_START_HEAL; int ACT_VORTIGAUNT_HEAL_LOOP; int ACT_VORTIGAUNT_END_HEAL; int ACT_VORTIGAUNT_TO_ACTION; int ACT_VORTIGAUNT_TO_IDLE; int ACT_VORTIGAUNT_STOMP; int ACT_VORTIGAUNT_DEFEND; int ACT_VORTIGAUNT_TO_DEFEND; int ACT_VORTIGAUNT_FROM_DEFEND; //========================================================= // Monster's Anim Events Go Here //========================================================= int AE_VORTIGAUNT_CLAW_LEFT; int AE_VORTIGAUNT_CLAW_RIGHT; int AE_VORTIGAUNT_ZAP_POWERUP; int AE_VORTIGAUNT_ZAP_SHOOT; int AE_VORTIGAUNT_ZAP_DONE; int AE_VORTIGAUNT_HEAL_STARTGLOW; int AE_VORTIGAUNT_HEAL_STARTBEAMS; int AE_VORTIGAUNT_KICK; int AE_VORTIGAUNT_STOMP; int AE_VORTIGAUNT_HEAL_STARTSOUND; int AE_VORTIGAUNT_SWING_SOUND; int AE_VORTIGAUNT_SHOOT_SOUNDSTART; int AE_VORTIGAUNT_DEFEND_BEAMS; //----------------------------------------------------------------------------- // Interactions //----------------------------------------------------------------------------- int g_interactionVortigauntStomp = 0; int g_interactionVortigauntStompFail = 0; int g_interactionVortigauntStompHit = 0; int g_interactionVortigauntKick = 0; int g_interactionVortigauntClaw = 0; //--------------------------------------------------------- // Save/Restore //--------------------------------------------------------- BEGIN_DATADESC( CNPC_Vortigaunt ) DEFINE_FIELD( m_flNextNPCThink, FIELD_TIME), DEFINE_ARRAY( m_pBeam, FIELD_EHANDLE, VORTIGAUNT_MAX_BEAMS ), DEFINE_FIELD( m_iBeams, FIELD_INTEGER), DEFINE_FIELD( m_nLightningSprite, FIELD_INTEGER), DEFINE_FIELD( m_fGlowAge, FIELD_FLOAT), DEFINE_FIELD( m_fGlowScale, FIELD_FLOAT), DEFINE_FIELD( m_fGlowChangeTime, FIELD_FLOAT), DEFINE_FIELD( m_bGlowTurningOn, FIELD_BOOLEAN), DEFINE_FIELD( m_nCurGlowIndex, FIELD_INTEGER), DEFINE_FIELD( m_pLeftHandGlow, FIELD_EHANDLE), DEFINE_FIELD( m_pRightHandGlow, FIELD_EHANDLE), DEFINE_FIELD( m_flNextHealTime, FIELD_TIME), DEFINE_FIELD( m_nCurrentHealAmt, FIELD_INTEGER), DEFINE_FIELD( m_nLastArmorAmt, FIELD_INTEGER), DEFINE_FIELD( m_iSuitSound, FIELD_INTEGER), DEFINE_FIELD( m_flSuitSoundTime, FIELD_TIME), DEFINE_FIELD( m_painTime, FIELD_TIME), DEFINE_FIELD( m_nextLineFireTime, FIELD_TIME), DEFINE_FIELD( m_bInBarnacleMouth, FIELD_BOOLEAN), DEFINE_KEYFIELD( m_bArmorRechargeEnabled, FIELD_BOOLEAN, "ArmorRechargeEnabled" ), DEFINE_FIELD( m_bForceArmorRecharge, FIELD_BOOLEAN), DEFINE_FIELD( m_iCurrentRechargeGoal, FIELD_INTEGER ), DEFINE_FIELD( m_hVictim, FIELD_EHANDLE ), DEFINE_FIELD( m_bExtractingBugbait, FIELD_BOOLEAN), DEFINE_FIELD( m_iLeftHandAttachment, FIELD_INTEGER ), DEFINE_FIELD( m_iRightHandAttachment, FIELD_INTEGER ), DEFINE_FIELD( m_hHealTarget, FIELD_EHANDLE ), // m_AssaultBehavior (auto saved by AI) // m_LeadBehavior (auto saved by AI) // DEFINE_FIELD( m_bStopLoopingSounds, FIELD_BOOLEAN ), // Function Pointers DEFINE_USEFUNC( Use ), DEFINE_INPUTFUNC( FIELD_VOID, "EnableArmorRecharge", InputEnableArmorRecharge ), DEFINE_INPUTFUNC( FIELD_VOID, "DisableArmorRecharge", InputDisableArmorRecharge ), DEFINE_INPUTFUNC( FIELD_STRING, "ChargeTarget", InputChargeTarget ), DEFINE_INPUTFUNC( FIELD_STRING, "ExtractBugbait", InputExtractBugbait ), // Outputs DEFINE_OUTPUT(m_OnFinishedExtractingBugbait, "OnFinishedExtractingBugbait"), DEFINE_OUTPUT(m_OnFinishedChargingTarget, "OnFinishedChargingTarget"), DEFINE_OUTPUT(m_OnPlayerUse, "OnPlayerUse" ), END_DATADESC() LINK_ENTITY_TO_CLASS( npc_vortigaunt, CNPC_Vortigaunt ); // for special behavior with rollermines static bool IsRoller( CBaseEntity *pRoller ) { return FClassnameIs( pRoller, "npc_rollermine" ); } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- bool CNPC_Vortigaunt::IsHealPositionValid(void) { CBasePlayer *pPlayer = AI_GetSinglePlayer(); if ( !pPlayer ) return false; Vector vecToPlayer = ( pPlayer->EyePosition() - EyePosition() ); // Make sure he's still within heal range if ( vecToPlayer.LengthSqr() > (HEAL_RANGE*HEAL_RANGE) ) return false; // Make sure the player stays in front of me. // Don't do a straight viewcone check, because my head turns to follow the player, and // that will allow the player to move too far around me. Instead, do a viewcone check // from my base facing. vecToPlayer.z = 0; VectorNormalize( vecToPlayer ); Vector facingDir = BodyDirection2D(); float flDot = DotProduct( vecToPlayer, facingDir ); if ( flDot < VIEW_FIELD_NARROW ) return false; // Now ensure he's still visible return ( FVisible( pPlayer ) ); } //----------------------------------------------------------------------------- // Purpose: Check the heal position. If it's bad, break out current schedule // and setup for healing at a later time. //----------------------------------------------------------------------------- bool CNPC_Vortigaunt::CheckHealPosition( void ) { if ( IsHealPositionValid() ) return true; // Heal position isn't valid. Abort heal. Speak( VORT_CURE_INTERRUPT ); ClearBeams(); EndHandGlow(); m_flNextHealTime = gpGlobals->curtime + 5.0; TaskFail(FAIL_BAD_POSITION); return false; } //------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ #define VORT_HEAL_SENTENCE 0 #define VORT_DONE_HEAL_SENTENCE 1 #define VORT_START_EXTRACT_SENTENCE 2 #define VORT_FINISH_EXTRACT_SENTENCE 3 void CNPC_Vortigaunt::SpeakSentence( int sentenceType ) { if (sentenceType == VORT_HEAL_SENTENCE) { if ( IsHealPositionValid() ) { Speak( VORT_CURE ); } } else if (sentenceType == VORT_DONE_HEAL_SENTENCE) { Speak( VORT_CURESTOP ); } else if (sentenceType == VORT_START_EXTRACT_SENTENCE) { Speak( VORT_EXTRACT_START ); } else if (sentenceType == VORT_FINISH_EXTRACT_SENTENCE) { Speak( VORT_EXTRACT_FINISH ); } } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- void CNPC_Vortigaunt::StartTask( const Task_t *pTask ) { switch ( pTask->iTask) { case TASK_VORTIGAUNT_GET_HEAL_TARGET: { // Sets our target to the entity that we cached earlier. if ( !m_hHealTarget ) { TaskFail( FAIL_NO_TARGET ); } else { SetTarget( m_hHealTarget ); TaskComplete(); } break; } case TASK_VORTIGAUNT_EXTRACT_WARMUP: { ResetIdealActivity( (Activity) ACT_VORTIGAUNT_TO_ACTION ); break; } case TASK_VORTIGAUNT_EXTRACT: { SetActivity( (Activity) ACT_RANGE_ATTACK1 ); break; } case TASK_VORTIGAUNT_EXTRACT_COOLDOWN: { ResetIdealActivity( (Activity)ACT_VORTIGAUNT_TO_IDLE ); break; } case TASK_VORTIGAUNT_FIRE_EXTRACT_OUTPUT: { // Cheat, and fire both outputs m_OnFinishedExtractingBugbait.FireOutput( this, this ); TaskComplete(); break; } case TASK_VORTIGAUNT_HEAL_WARMUP: { ResetIdealActivity( (Activity)ACT_VORTIGAUNT_START_HEAL ); break; } case TASK_VORTIGAUNT_HEAL: { ResetIdealActivity( (Activity)ACT_VORTIGAUNT_HEAL_LOOP ); break; } case TASK_VORTIGAUNT_FACE_STOMP: { if (GetEnemy()!=NULL) { GetMotor()->SetIdealYaw( CalcIdealYaw ( GetEnemy()->GetLocalOrigin() ) + VORTIGAUNT_STOMP_TURN_OFFSET ); SetTurnActivity(); } else { TaskFail(FAIL_NO_ENEMY); } break; } case TASK_VORTIGAUNT_STOMP_ATTACK: { m_flLastAttackTime = gpGlobals->curtime; ResetIdealActivity( (Activity)ACT_VORTIGAUNT_STOMP ); break; } case TASK_VORTIGAUNT_GRENADE_KILL: { if (GetTarget() == NULL) { TaskComplete(); } // Used to set delay between beam shot and damage ClearWait(); break; } case TASK_VORTIGAUNT_ZAP_GRENADE_OWNER: { if (GetEnemy() == NULL) { TaskComplete(); } // Used to set delay between beam shot and damage ClearWait(); break; } case TASK_VORTIGAUNT_WAIT_FOR_PLAYER: { // Wait for the player to get near (before starting the bugbait sequence) break; } default: { BaseClass::StartTask( pTask ); break; } } } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- void CNPC_Vortigaunt::RunTask( const Task_t *pTask ) { switch ( pTask->iTask ) { case TASK_RANGE_ATTACK1: { if (GetEnemy() != NULL) { if (GetEnemy()->IsPlayer()) { m_flPlaybackRate = 1.5; } if (!GetEnemy()->IsAlive()) { if( IsActivityFinished() ) { TaskComplete(); break; } } // This is along attack sequence so if the enemy // becomes occluded bail if (HasCondition( COND_ENEMY_OCCLUDED )) { TaskComplete(); break; } } BaseClass::RunTask( pTask ); break; } case TASK_MELEE_ATTACK1: { if (GetEnemy() == NULL) { if ( IsActivityFinished() ) { TaskComplete(); } } else { BaseClass::RunTask( pTask ); } break; } case TASK_MELEE_ATTACK2: { if ( IsActivityFinished() ) { TaskComplete(); } break; } case TASK_VORTIGAUNT_STOMP_ATTACK: { // Face enemy slightly off center if (GetEnemy() != NULL) { GetMotor()->SetIdealYawAndUpdate( CalcIdealYaw ( GetEnemy()->GetLocalOrigin() ) + VORTIGAUNT_STOMP_TURN_OFFSET, AI_KEEP_YAW_SPEED ); } if ( IsActivityFinished() ) { TaskComplete(); } break; } case TASK_VORTIGAUNT_FACE_STOMP: { if (GetEnemy()!=NULL) { GetMotor()->SetIdealYawAndUpdate( CalcIdealYaw( GetEnemy()->GetLocalOrigin() ) + VORTIGAUNT_STOMP_TURN_OFFSET ); if ( FacingIdeal() ) { TaskComplete(); } } else { TaskFail(FAIL_NO_ENEMY); } break; } case TASK_VORTIGAUNT_EXTRACT_WARMUP: { if ( IsActivityFinished() ) { TaskComplete(); } break; } case TASK_VORTIGAUNT_EXTRACT: { if ( IsActivityFinished() ) { TaskComplete(); } break; } case TASK_VORTIGAUNT_EXTRACT_COOLDOWN: { if ( IsActivityFinished() ) { m_bExtractingBugbait = false; TaskComplete(); } break; } case TASK_VORTIGAUNT_HEAL_WARMUP: { if ( IsActivityFinished() ) { TaskComplete(); } if ( !CheckHealPosition() ) return; break; } case TASK_VORTIGAUNT_GRENADE_KILL: { if (GetTarget() == NULL) { TaskComplete(); break; } // Face the grenade GetMotor()->SetIdealYawToTargetAndUpdate( GetTarget()->GetLocalOrigin() ); // Shoot beam when grenade is close float flDist = (GetTarget()->GetLocalOrigin() - GetLocalOrigin()).Length(); if (flDist < 225) { ClawBeam(GetTarget(),16, m_iLeftHandAttachment ); ClawBeam(GetTarget(),16, m_iRightHandAttachment ); } if (flDist < 150) { CBaseGrenade* pGrenade = dynamic_cast<CBaseGrenade*>((CBaseEntity*)GetTarget()); if (!pGrenade) { TaskComplete(); } pGrenade->Detonate(); TaskComplete(); } break; } case TASK_VORTIGAUNT_ZAP_GRENADE_OWNER: { if (GetEnemy() == NULL) { TaskComplete(); break; } // Face the grenade owner GetMotor()->SetIdealYawToTargetAndUpdate( GetEnemy()->GetLocalOrigin() ); // Shoot beam only if enemy is close enough float flDist = (GetEnemy()->GetLocalOrigin() - GetLocalOrigin()).Length(); if (flDist < 350) { if (!IsWaitSet() || IsWaitFinished() ) { ClawBeam(GetEnemy(),5, m_iLeftHandAttachment ); ClawBeam(GetEnemy(),5, m_iRightHandAttachment ); // Set a delay and then we do damage. Store in // m_hVictim in case we are interrupted if (!IsWaitSet()) { SetWait( 0.2 ); m_hVictim = GetEnemy(); } } else { if (m_hVictim != NULL) { CTakeDamageInfo info( this, this, 15, DMG_SHOCK ); CalculateMeleeDamageForce( &info, (GetEnemy()->GetAbsOrigin() - GetAbsOrigin()), GetEnemy()->GetAbsOrigin() ); m_hVictim->MyNPCPointer()->TakeDamage( info ); } TaskComplete(); } } else { // To far away to shoot TaskComplete(); } break; } case TASK_VORTIGAUNT_HEAL: { CBasePlayer *pPlayer = AI_GetSinglePlayer(); if (pPlayer) { if (!CheckHealPosition()) return; // Charge the suit's armor pPlayer->IncrementArmorValue( 1, m_iCurrentRechargeGoal ); // Stop healing once we've hit the maximum level we'll ever charge the player to. if ( pPlayer->ArmorValue() >= m_iCurrentRechargeGoal ) { m_bForceArmorRecharge = false; m_flNextHealTime = gpGlobals->curtime + VORTIGAUNT_HEAL_RECHARGE; ClearBeams(); EndHandGlow(); TaskComplete(); m_OnFinishedChargingTarget.FireOutput( this, this ); return; } // ------------------------------------------ // Suit sound // ------------------------------------------ // Play the on sound or the looping charging sound if (!m_iSuitSound) { m_iSuitSound++; EmitSound( "NPC_Vortigaunt.SuitOn" ); m_flSuitSoundTime = 0.56 + gpGlobals->curtime; } if ((m_iSuitSound == 1) && (m_flSuitSoundTime <= gpGlobals->curtime)) { m_iSuitSound++; CPASAttenuationFilter filter( this, "NPC_Vortigaunt.SuitCharge" ); filter.MakeReliable(); EmitSound( filter, entindex(), "NPC_Vortigaunt.SuitCharge" ); m_bStopLoopingSounds = true; } } break; } case TASK_VORTIGAUNT_WAIT_FOR_PLAYER: { // Wait for the player to get near (before starting the bugbait sequence) // Get edict for one player CBasePlayer *pPlayer = AI_GetSinglePlayer(); if ( pPlayer ) { GetMotor()->SetIdealYawToTargetAndUpdate( pPlayer->GetAbsOrigin(), AI_KEEP_YAW_SPEED ); SetTurnActivity(); if ( GetMotor()->DeltaIdealYaw() < 10 ) { // Wait for the player to get close enough if ( UTIL_DistApprox( GetAbsOrigin(), pPlayer->GetAbsOrigin() ) < 384 ) { TaskComplete(); } } } else { TaskFail(FAIL_NO_PLAYER); } } break; default: { BaseClass::RunTask( pTask ); break; } } } //========================================================= // GetSoundInterests - returns a bit mask indicating which types // of sounds this monster regards. //========================================================= int CNPC_Vortigaunt::GetSoundInterests ( void) { return SOUND_WORLD | SOUND_COMBAT | SOUND_CARCASS | SOUND_MEAT | SOUND_GARBAGE | SOUND_DANGER | SOUND_PLAYER; } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- Class_T CNPC_Vortigaunt::Classify ( void ) { return CLASS_VORTIGAUNT; } //========================================================= // ALertSound - barney says "Freeze!" //========================================================= void CNPC_Vortigaunt::AlertSound( void ) { if ( GetEnemy() != NULL ) { if ( IsOkToCombatSpeak() ) { Speak( VORT_ATTACK ); } } } //========================================================= // MaxYawSpeed - allows each sequence to have a different // turn rate associated with it. //========================================================= float CNPC_Vortigaunt::MaxYawSpeed ( void ) { switch ( GetActivity() ) { case ACT_IDLE: return 35; break; case ACT_WALK: return 35; break; case ACT_RUN: return 45; break; default: return 35; break; } } //------------------------------------------------------------------------------ // Purpose : For innate range attack // Input : // Output : //------------------------------------------------------------------------------ int CNPC_Vortigaunt::RangeAttack1Conditions( float flDot, float flDist ) { if (GetEnemy() == NULL) { return( COND_NONE ); } if ( gpGlobals->curtime < m_flNextAttack ) { return( COND_NONE ); } // Range attack is ineffective on manhack so never use it // Melee attack looks a lot better anyway if (GetEnemy()->Classify() == CLASS_MANHACK) { return( COND_NONE ); } // dvs: Allow up-close range attacks for episodic as the vort's melee // attack is rather ineffective. #ifndef HL2_EPISODIC if ( flDist <= 70 ) { return( COND_TOO_CLOSE_TO_ATTACK ); } else #endif // HL2_EPISODIC if ( flDist > 1500 * 12 ) // 1500ft max { return( COND_TOO_FAR_TO_ATTACK ); } else if ( flDot < 0.65 ) { return( COND_NOT_FACING_ATTACK ); } return( COND_CAN_RANGE_ATTACK1 ); } //----------------------------------------------------------------------------- // Purpose: For innate melee attack // Input : // Output : //----------------------------------------------------------------------------- int CNPC_Vortigaunt::MeleeAttack1Conditions ( float flDot, float flDist ) { if (flDist > 70) { return COND_TOO_FAR_TO_ATTACK; } else if (flDot < 0.7) { // If I'm not facing attack clear TOOFAR which may have been set by range attack // Otherwise we may try to back away even when melee attack is valid ClearCondition(COND_TOO_FAR_TO_ATTACK); return COND_NOT_FACING_ATTACK; } // no melee attacks against rollermins if ( IsRoller(GetEnemy()) ) return COND_NONE; return COND_CAN_MELEE_ATTACK1; } //------------------------------------------------------------------------------ // Purpose : Returns who I hit on a kick (or NULL) // Input : // Output : //------------------------------------------------------------------------------ CBaseEntity* CNPC_Vortigaunt::Kick( void ) { trace_t tr; Vector forward; AngleVectors( GetAbsAngles(), &forward ); Vector vecStart = WorldSpaceCenter(); Vector vecEnd = vecStart + (forward * 70); AI_TraceHull( vecStart, vecEnd, Vector(-16,-16,-18), Vector(16,16,18), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr ); return tr.m_pEnt; } //------------------------------------------------------------------------------ // Purpose : Vortigaunt claws at his enemy // Input : // Output : //------------------------------------------------------------------------------ void CNPC_Vortigaunt::Claw( int iAttachment) { CBaseEntity *pHurt = CheckTraceHullAttack( 40, Vector(-10,-10,-10), Vector(10,10,10),sk_vortigaunt_dmg_claw.GetFloat(), DMG_SLASH ); if ( pHurt ) { pHurt->ViewPunch( QAngle(5,0,-18) ); // If hit manhack make temp glow if (pHurt->Classify() == CLASS_MANHACK) { ClawBeam( pHurt, 16, iAttachment ); } // Play a random attack hit sound EmitSound( "NPC_Vortigaunt.Claw" ); } } //========================================================= // 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 ) { if ( pEvent->event == AE_VORTIGAUNT_CLAW_LEFT ) { Claw( m_iLeftHandAttachment ); return; } if ( pEvent->event == AE_VORTIGAUNT_CLAW_RIGHT ) { Claw( m_iRightHandAttachment ); return; } if ( pEvent->event == AE_VORTIGAUNT_ZAP_POWERUP ) { // speed up attack when on hard if ( g_iSkillLevel == SKILL_HARD ) m_flPlaybackRate = 1.5; ArmBeam( -1 ,VORTIGAUNT_BEAM_HURT ); ArmBeam( 1 ,VORTIGAUNT_BEAM_HURT ); BeamGlow(); // Make hands glow if not already glowing if ( m_fGlowAge == 0 ) { StartHandGlow( VORTIGAUNT_BEAM_HURT ); } CPASAttenuationFilter filter( this ); CSoundParameters params; if ( GetParametersForSound( "NPC_Vortigaunt.ZapPowerup", params, NULL ) ) { EmitSound_t ep( params ); ep.m_nPitch = 100 + m_iBeams * 10; EmitSound( filter, entindex(), ep ); m_bStopLoopingSounds = true; } //m_nSkin = m_iBeams / 2; return; } if ( pEvent->event == AE_VORTIGAUNT_ZAP_SHOOT ) { ClearBeams(); ClearMultiDamage(); ZapBeam( -1 ); ZapBeam( 1 ); EndHandGlow(); EmitSound( "NPC_Vortigaunt.Shoot" ); m_bStopLoopingSounds = true; ApplyMultiDamage(); if ( m_bExtractingBugbait == true ) { // Spawn bugbait! CBaseCombatWeapon *pWeapon = Weapon_Create( "weapon_bugbait" ); if ( pWeapon ) { // Starting above the body, spawn closer and closer to the vort until it's clear Vector vecSpawnOrigin = GetTarget()->WorldSpaceCenter() + Vector(0, 0, 32); int iNumAttempts = 4; Vector vecToVort = (WorldSpaceCenter() - vecSpawnOrigin); float flDistance = VectorNormalize( vecToVort ) / (iNumAttempts-1); int i = 0; for (; i < iNumAttempts; i++ ) { trace_t tr; CTraceFilterSkipTwoEntities traceFilter( GetTarget(), this, COLLISION_GROUP_NONE ); AI_TraceLine( vecSpawnOrigin, vecSpawnOrigin, MASK_SHOT, &traceFilter, &tr ); if ( tr.fraction == 1.0 && !tr.m_pEnt ) { // Make sure it can fit there AI_TraceHull( vecSpawnOrigin, vecSpawnOrigin, -Vector(16,16,16), Vector(16,16,48), MASK_SHOT, &traceFilter, &tr ); if ( tr.fraction == 1.0 && !tr.m_pEnt ) break; } //NDebugOverlay::Box( vecSpawnOrigin, pWeapon->WorldAlignMins(), pWeapon->WorldAlignMins(), 255,0,0, 64, 100 ); // Move towards the vort vecSpawnOrigin = vecSpawnOrigin + (vecToVort * flDistance); } // HACK: If we've still failed, just spawn it on the player if ( i == iNumAttempts ) { CBasePlayer *pPlayer = AI_GetSinglePlayer(); if ( pPlayer ) { vecSpawnOrigin = pPlayer->WorldSpaceCenter(); } } //NDebugOverlay::Box( vecSpawnOrigin, -Vector(20,20,20), Vector(20,20,20), 0,255,0, 64, 100 ); pWeapon->SetAbsOrigin( vecSpawnOrigin ); pWeapon->Drop( Vector(0,0,1) ); } CEffectData data; data.m_vOrigin = GetTarget()->WorldSpaceCenter(); data.m_vNormal = WorldSpaceCenter() - GetTarget()->WorldSpaceCenter(); VectorNormalize( data.m_vNormal ); data.m_flScale = 4; DispatchEffect( "AntlionGib", data ); } m_flNextAttack = gpGlobals->curtime + random->RandomFloat( 0.5, 4.0 ); return; } if ( pEvent->event == AE_VORTIGAUNT_ZAP_DONE ) { ClearBeams( ); return; } if ( pEvent->event == AE_VORTIGAUNT_HEAL_STARTGLOW ) { // Make hands glow StartHandGlow(VORTIGAUNT_BEAM_HEAL); return; } if ( pEvent->event == AE_VORTIGAUNT_HEAL_STARTBEAMS ) { if ( !CheckHealPosition()) return; HealBeam(-1); return; //m_nSkin = m_iBeams / 2; } if( pEvent->event == AE_VORTIGAUNT_KICK ) { CBaseEntity *pHurt = Kick(); CBaseCombatCharacter* pBCC = ToBaseCombatCharacter( pHurt ); if (pBCC) { Vector forward, up; AngleVectors( GetLocalAngles(), &forward, NULL, &up ); if ( pBCC->DispatchInteraction( g_interactionVortigauntKick, NULL, this ) ) { pBCC->ApplyAbsVelocityImpulse( forward * 300 + up * 250 ); CTakeDamageInfo info( this, this, pBCC->m_iHealth+1, DMG_CLUB ); CalculateMeleeDamageForce( &info, forward, pHurt->GetAbsOrigin() ); pBCC->TakeDamage( info ); } else { pBCC->ViewPunch( QAngle(15,0,0) ); CTakeDamageInfo info( this, this, sk_vortigaunt_dmg_claw.GetFloat(), DMG_CLUB ); CalculateMeleeDamageForce( &info, forward, pHurt->GetAbsOrigin() ); pBCC->TakeDamage( info ); } EmitSound( "NPC_Vortigaunt.Kick" ); } return; } if ( pEvent->event == AE_VORTIGAUNT_STOMP ) { CBaseCombatCharacter* pBCC = GetEnemyCombatCharacterPointer(); if ( pBCC ) { float fDist = (pBCC->GetLocalOrigin() - GetLocalOrigin()).Length(); if (fDist > VORTIGAUNT_STOMP_DIST) { pBCC->DispatchInteraction( g_interactionVortigauntStompFail, NULL, this ); return; } else { pBCC->DispatchInteraction( g_interactionVortigauntStompHit, NULL, this ); } EmitSound( "NPC_Vortigaunt.Kick" ); } return; } if ( pEvent->event == AE_VORTIGAUNT_HEAL_STARTSOUND ) { CPASAttenuationFilter filter( this ); CSoundParameters params; if ( GetParametersForSound( "NPC_Vortigaunt.StartHealLoop", params, NULL ) ) { EmitSound_t ep( params ); ep.m_nPitch = 100 + m_iBeams * 10; EmitSound( filter, entindex(), ep ); m_bStopLoopingSounds = true; } return; } if ( pEvent->event == AE_VORTIGAUNT_SWING_SOUND ) { EmitSound( "NPC_Vortigaunt.Swing" ); return; } if ( pEvent->event == AE_VORTIGAUNT_SHOOT_SOUNDSTART ) { CPASAttenuationFilter filter( this ); CSoundParameters params; if ( GetParametersForSound( "NPC_Vortigaunt.StartShootLoop", params, NULL ) ) { EmitSound_t ep( params ); ep.m_nPitch = 100 + m_iBeams * 10; EmitSound( filter, entindex(), ep ); m_bStopLoopingSounds = true; } return; } if ( pEvent->event == AE_VORTIGAUNT_DEFEND_BEAMS ) { DefendBeams(); return; } if ( pEvent->event == AE_NPC_LEFTFOOT ) { EmitSound( "NPC_Vortigaunt.FootstepLeft", pEvent->eventtime ); return; } if ( pEvent->event == AE_NPC_RIGHTFOOT ) { EmitSound( "NPC_Vortigaunt.FootstepRight", pEvent->eventtime ); return; } BaseClass::HandleAnimEvent( pEvent ); } //------------------------------------------------------------------------------ // Purpose : Returnst true if entity is stompable // Input : // Output : //------------------------------------------------------------------------------ bool CNPC_Vortigaunt::IsStompable(CBaseEntity *pEntity) { if (!pEntity) return false; float fDist = (pEntity->GetLocalOrigin() - GetLocalOrigin()).Length(); if (fDist > VORTIGAUNT_STOMP_DIST) return false; CBaseCombatCharacter* pBCC = (CBaseCombatCharacter *)pEntity; if ( pBCC && pBCC->DispatchInteraction( g_interactionVortigauntStomp, NULL, this ) ) return true; return false; } //------------------------------------------------------------------------------ // Purpose : Translate some activites for the Vortigaunt // Input : // Output : //------------------------------------------------------------------------------ Activity CNPC_Vortigaunt::NPC_TranslateActivity( Activity eNewActivity ) { if ((eNewActivity == ACT_SIGNAL3) && (SelectWeightedSequence ( ACT_SIGNAL3 ) == ACTIVITY_NOT_AVAILABLE) ) { eNewActivity = ACT_IDLE; } if (eNewActivity == ACT_IDLE) { if ( m_NPCState == NPC_STATE_COMBAT || m_NPCState == NPC_STATE_ALERT) { return ACT_IDLE_ANGRY; } } else if (eNewActivity == ACT_MELEE_ATTACK1) { // If enemy is low pick ATTACK2 (kick) if (GetEnemy() != NULL && (GetEnemy()->EyePosition().z - GetLocalOrigin().z) < 20) { return ACT_MELEE_ATTACK2; } } return BaseClass::NPC_TranslateActivity( eNewActivity ); } //------------------------------------------------------------------------------ // Purpose : If I've been in alert for a while and nothing's happened, // go back to idle // Input : // Output : //------------------------------------------------------------------------------ bool CNPC_Vortigaunt::ShouldGoToIdleState( void ) { if (m_flLastStateChangeTime + 10 < gpGlobals->curtime) { return true; } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_Vortigaunt::UpdateOnRemove( void) { ClearBeams(); ClearHandGlow(); // Chain at end to mimic destructor unwind order BaseClass::UpdateOnRemove(); } //------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ void CNPC_Vortigaunt::Event_Killed( const CTakeDamageInfo &info ) { ClearBeams(); ClearHandGlow(); BaseClass::Event_Killed( info ); } bool CNPC_Vortigaunt::CreateBehaviors() { AddBehavior( &m_AssaultBehavior ); AddBehavior( &m_LeadBehavior ); //AddBehavior( &m_FollowBehavior ); return BaseClass::CreateBehaviors(); } //========================================================= // Spawn //========================================================= void CNPC_Vortigaunt::Spawn() { // Allow multiple models (for slaves), but default to vortigaunt.mdl char *szModel = (char *)STRING( GetModelName() ); if (!szModel || !*szModel) { szModel = "models/vortigaunt.mdl"; SetModelName( AllocPooledString(szModel) ); } Precache(); SetModel( szModel ); SetHullType(HULL_WIDE_HUMAN); SetHullSizeNormal(); SetSolid( SOLID_BBOX ); AddSolidFlags( FSOLID_NOT_STANDABLE ); SetMoveType( MOVETYPE_STEP ); m_bloodColor = BLOOD_COLOR_GREEN; m_iHealth = sk_vortigaunt_health.GetFloat(); SetViewOffset( Vector ( 0, 0, 64 ) );// position of the eyes relative to monster's origin. m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so npc will notice player and say hello m_NPCState = NPC_STATE_NONE; GetExpresser()->SetVoicePitch( random->RandomInt( 85, 110 ) ); CapabilitiesAdd( bits_CAP_ANIMATEDFACE | bits_CAP_TURN_HEAD | bits_CAP_MOVE_GROUND | bits_CAP_NO_HIT_PLAYER ); CapabilitiesAdd ( bits_CAP_INNATE_RANGE_ATTACK1 ); CapabilitiesAdd ( bits_CAP_INNATE_MELEE_ATTACK1 ); CapabilitiesAdd ( bits_CAP_DOORS_GROUP ); CapabilitiesAdd( bits_CAP_FRIENDLY_DMG_IMMUNE ); m_flNextHealTime = 0; // Next time allowed to heal player m_flEyeIntegRate = 0.6; // Got a big eyeball so turn it slower m_hVictim = NULL; m_bForceArmorRecharge = false; m_nCurGlowIndex = 0; m_pLeftHandGlow = NULL; m_pRightHandGlow = NULL; m_bStopLoopingSounds = false; m_iLeftHandAttachment = LookupAttachment( VORTIGAUNT_LEFT_CLAW ); m_iRightHandAttachment = LookupAttachment( VORTIGAUNT_RIGHT_CLAW ); NPCInit(); SetUse( &CNPC_Vortigaunt::Use ); } //========================================================= // Precache - precaches all resources this monster needs //========================================================= void CNPC_Vortigaunt::Precache() { PrecacheModel( STRING( GetModelName() ) ); m_nLightningSprite = PrecacheModel("sprites/lgtning.vmt"); PrecacheModel("sprites/blueglow1.vmt"); PrecacheModel("sprites/greenglow1.vmt"); // every new barney must call this, otherwise // when a level is loaded, nobody will talk (time is reset to 0) TalkInit(); PrecacheScriptSound( "NPC_Vortigaunt.SuitOn" ); PrecacheScriptSound( "NPC_Vortigaunt.SuitCharge" ); PrecacheScriptSound( "NPC_Vortigaunt.Claw" ); PrecacheScriptSound( "NPC_Vortigaunt.ZapPowerup" ); PrecacheScriptSound( "NPC_Vortigaunt.Shoot" ); PrecacheScriptSound( "NPC_Vortigaunt.Kick" ); PrecacheScriptSound( "NPC_Vortigaunt.StartHealLoop" ); PrecacheScriptSound( "NPC_Vortigaunt.Swing" ); PrecacheScriptSound( "NPC_Vortigaunt.StartShootLoop" ); PrecacheScriptSound( "NPC_Vortigaunt.FootstepLeft" ); PrecacheScriptSound( "NPC_Vortigaunt.FootstepRight" ); PrecacheScriptSound( "NPC_Vortigaunt.ClawBeam" ); BaseClass::Precache(); } // Init talk data void CNPC_Vortigaunt::TalkInit() { BaseClass::TalkInit(); // vortigaunt will try to talk to friends in this order: m_szFriends[0] = "npc_vortigaunt"; m_szFriends[1] = "npc_citizen"; // get voice for head - just one barney voice for now GetExpresser()->SetVoicePitch( 100 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_Vortigaunt::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { m_OnPlayerUse.FireOutput( pActivator, pCaller ); if ( !IsInAScript() && m_NPCState != NPC_STATE_SCRIPT ) { // First, try to speak the +USE concept if ( IsOkToSpeakInResponseToPlayer() ) { if ( !Speak( TLK_USE ) ) { // If we haven't said hi, say that first if ( !SpokeConcept( TLK_HELLO ) ) { Speak( TLK_HELLO ); } else { Speak( TLK_IDLE ); } } else { // Don't say hi after you've said your +USE speech SetSpokeConcept( TLK_HELLO, NULL ); } } } } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- int CNPC_Vortigaunt::OnTakeDamage_Alive( const CTakeDamageInfo &info ) { // make sure friends talk about it if player hurts talkmonsters... int ret = BaseClass::OnTakeDamage_Alive( info ); if (!IsAlive()) { return ret; } if ( m_NPCState != NPC_STATE_PRONE && (info.GetAttacker()->GetFlags() & FL_CLIENT) ) { // This is a heurstic to determine if the player intended to harm me // If I have an enemy, we can't establish intent (may just be crossfire) if ( GetEnemy() == NULL ) { // If I'm already suspicious, get mad if (m_afMemory & bits_MEMORY_SUSPICIOUS) { // Alright, now I'm pissed! Speak( VORT_MAD ); Remember( bits_MEMORY_PROVOKED ); // Allowed to hit the player now! CapabilitiesRemove(bits_CAP_NO_HIT_PLAYER); StopFollowing(); } else { // Hey, be careful with that Speak( VORT_SHOT ); Remember( bits_MEMORY_SUSPICIOUS ); } } else if ( !(GetEnemy()->IsPlayer()) && (m_lifeState != LIFE_DEAD )) { Speak( VORT_SHOT ); } } return ret; } //========================================================= // PainSound //========================================================= void CNPC_Vortigaunt::PainSound( const CTakeDamageInfo &info ) { if (gpGlobals->curtime < m_painTime) return; m_painTime = gpGlobals->curtime + random->RandomFloat(0.5, 0.75); Speak( VORT_PAIN ); } //========================================================= // DeathSound //========================================================= void CNPC_Vortigaunt::DeathSound( const CTakeDamageInfo &info ) { Speak( VORT_DIE ); } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- void CNPC_Vortigaunt::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr ) { CTakeDamageInfo info = inputInfo; if ( (info.GetDamageType() & DMG_SHOCK) && FClassnameIs( info.GetAttacker(), GetClassname() ) ) { // mask off damage from other vorts for now info.SetDamage( 0.01 ); } switch( ptr->hitgroup) { case HITGROUP_CHEST: case HITGROUP_STOMACH: if (info.GetDamageType() & (DMG_BULLET | DMG_SLASH | DMG_BLAST)) { info.ScaleDamage( 0.5f ); } break; case 10: if (info.GetDamageType() & (DMG_BULLET | DMG_SLASH | DMG_CLUB)) { info.SetDamage( info.GetDamage() - 20 ); if (info.GetDamage() <= 0) { g_pEffects->Ricochet( ptr->endpos, (vecDir*-1.0f) ); info.SetDamage( 0.01 ); } } // always a head shot ptr->hitgroup = HITGROUP_HEAD; break; } BaseClass::TraceAttack( info, vecDir, ptr ); } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- int CNPC_Vortigaunt::TranslateSchedule( int scheduleType ) { int baseType; switch( scheduleType ) { case SCHED_RANGE_ATTACK1: return SCHED_VORTIGAUNT_RANGE_ATTACK; break; case SCHED_MELEE_ATTACK1: if (IsStompable(GetEnemy())) { return SCHED_VORTIGAUNT_STOMP_ATTACK; } else { // Tell my enemy I'm about to punch them so they can do something // special if they want to if ((GetEnemy() != NULL) && (GetEnemy()->MyNPCPointer() != NULL)) { Vector vFacingDir = BodyDirection2D( ); Vector vClawPos = EyePosition() + vFacingDir*30; GetEnemy()->MyNPCPointer()->DispatchInteraction( g_interactionVortigauntClaw, &vClawPos, this ); } return SCHED_VORTIGAUNT_MELEE_ATTACK; } break; case SCHED_IDLE_STAND: { // call base class default so that scientist will talk // when standing during idle baseType = BaseClass::TranslateSchedule(scheduleType); if (baseType == SCHED_IDLE_STAND) { // just look straight ahead return SCHED_VORTIGAUNT_STAND; } else return baseType; break; } case SCHED_FAIL_ESTABLISH_LINE_OF_FIRE: { // Fail schedule doesn't go through SelectSchedule() // So we have to clear beams and glow here ClearBeams(); EndHandGlow(); return SCHED_COMBAT_FACE; break; } case SCHED_CHASE_ENEMY_FAILED: { baseType = BaseClass::TranslateSchedule(scheduleType); if ( baseType != SCHED_CHASE_ENEMY_FAILED ) return baseType; if (HasMemory(bits_MEMORY_INCOVER)) { // Occasionally run somewhere so I don't just // stand around forever if my enemy is stationary if (random->RandomInt(0,5) == 5) { return SCHED_PATROL_RUN; } else { return SCHED_VORTIGAUNT_STAND; } } break; } case SCHED_FAIL_TAKE_COVER: { // Fail schedule doesn't go through SelectSchedule() // So we have to clear beams and glow here ClearBeams(); EndHandGlow(); return SCHED_RUN_RANDOM; break; } } return BaseClass::TranslateSchedule( scheduleType ); } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CNPC_Vortigaunt::ShouldHealTarget( void ) { if ( IsInAScript() ) return false; // Must be allowed to do this if ( m_bArmorRechargeEnabled == false ) return false; // Can't be too soon if ( m_flNextHealTime > gpGlobals->curtime ) return false; // Don't heal when fighting if ( m_NPCState == NPC_STATE_COMBAT ) return false; if ( GetEnemy() ) return false; if ( IsCurSchedule( SCHED_VORTIGAUNT_EXTRACT_BUGBAIT ) ) return false; // Can't heal if we're leading the player if ( IsLeading() ) return false; // Need to be looking at the player if ( !HasCondition( COND_SEE_PLAYER ) ) return false; // Can't be saying something // if ( GetExpresser()->IsSpeaking() ) // return false; // Find a likely target in range CBaseEntity *pEntity = PlayerInRange( GetLocalOrigin(), HEAL_RANGE ); if ( pEntity != NULL ) { CBasePlayer *pPlayer = ToBasePlayer( pEntity ); // Make sure the player's got a suit if ( !pPlayer->IsSuitEquipped() ) return false; if ( pPlayer->GetFlags() & FL_NOTARGET ) return false; // See if the player needs armor, or tau-cannon ammo if ( pPlayer->ArmorValue() < sk_vortigaunt_armor_charge.GetInt() ) { m_iCurrentRechargeGoal = pPlayer->ArmorValue() + sk_vortigaunt_armor_charge.GetInt(); if ( m_iCurrentRechargeGoal > 100 ) m_iCurrentRechargeGoal = 100; m_hHealTarget = pEntity; return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: // Output : int //----------------------------------------------------------------------------- int CNPC_Vortigaunt::SelectHealSchedule( void ) { // If our lead behavior has a goal, don't wait around to heal anyone if ( m_LeadBehavior.HasGoal() ) return SCHED_NONE; // See if we should heal the player if ( ShouldHealTarget() ) return SCHED_VORTIGAUNT_HEAL; return SCHED_NONE; } //------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ int CNPC_Vortigaunt::SelectSchedule( void ) { ClearBeams(); EndHandGlow(); // If we're currently supposed to be doing something scripted, do it immediately. if ( m_bExtractingBugbait ) return SCHED_VORTIGAUNT_EXTRACT_BUGBAIT; if ( m_bForceArmorRecharge ) { m_flNextHealTime = 0; return SCHED_VORTIGAUNT_HEAL; } int schedule = SelectHealSchedule(); if ( schedule != SCHED_NONE ) return schedule; if ( HasCondition( COND_PLAYER_PUSHING ) && GlobalEntity_GetState("gordon_precriminal") != GLOBAL_ON ) { return SCHED_MOVE_AWAY; } if ( BehaviorSelectSchedule() ) return BaseClass::SelectSchedule(); switch( m_NPCState ) { case NPC_STATE_PRONE: { if (m_bInBarnacleMouth) { return SCHED_VORTIGAUNT_BARNACLE_CHOMP; } else { return SCHED_VORTIGAUNT_BARNACLE_HIT; } } 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_HEAVY_DAMAGE ) ) { return SCHED_TAKE_COVER_FROM_ENEMY; } if ( HasCondition( COND_HEAR_DANGER ) ) { return SCHED_TAKE_COVER_FROM_BEST_SOUND; } if ( HasCondition( COND_ENEMY_DEAD ) && IsOkToCombatSpeak() ) { Speak( VORT_KILL ); } // If I might hit the player shooting... else if ( HasCondition( COND_WEAPON_PLAYER_IN_SPREAD )) { if ( IsOkToCombatSpeak() && m_nextLineFireTime < gpGlobals->curtime) { Speak( VORT_LINE_FIRE ); m_nextLineFireTime = gpGlobals->curtime + 3.0f; } // Run to a new location or stand and aim if (random->RandomInt(0,2) == 0) { return SCHED_ESTABLISH_LINE_OF_FIRE; } else { return SCHED_COMBAT_FACE; } } } break; case NPC_STATE_ALERT: case NPC_STATE_IDLE: if ( HasCondition( COND_HEAR_DANGER ) ) { return SCHED_TAKE_COVER_FROM_BEST_SOUND; } if ( HasCondition( COND_ENEMY_DEAD ) && IsOkToCombatSpeak() ) { Speak( VORT_KILL ); } break; } return BaseClass::SelectSchedule(); } //----------------------------------------------------------------------------- // Purpose: This is a generic function (to be implemented by sub-classes) to // handle specific interactions between different types of characters // (For example the barnacle grabbing an NPC) // Input : Constant for the type of interaction // Output : true - if sub-class has a response for the interaction // false - if sub-class has no response //----------------------------------------------------------------------------- bool CNPC_Vortigaunt::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt) { if (interactionType == g_interactionBarnacleVictimDangle) { // Force choosing of a new schedule ClearSchedule(); m_bInBarnacleMouth = true; return true; } else if ( interactionType == g_interactionBarnacleVictimReleased ) { SetIdealState( NPC_STATE_IDLE ); m_bInBarnacleMouth = false; SetAbsVelocity( vec3_origin ); SetMoveType( MOVETYPE_STEP ); return true; } else if ( interactionType == g_interactionBarnacleVictimGrab ) { if ( GetFlags() & FL_ONGROUND ) { SetGroundEntity( NULL ); } SetIdealState( NPC_STATE_PRONE ); CTakeDamageInfo info; PainSound( info ); return true; } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_Vortigaunt::DeclineFollowing( void ) { Speak( VORT_POK ); } //----------------------------------------------------------------------------- // Purpose: Return true if you're willing to be idly talked to by other friends. //----------------------------------------------------------------------------- bool CNPC_Vortigaunt::CanBeUsedAsAFriend( void ) { // We don't want to be used if we're busy if ( IsCurSchedule( SCHED_VORTIGAUNT_HEAL ) ) return false; if ( IsCurSchedule( SCHED_VORTIGAUNT_EXTRACT_BUGBAIT ) ) return false; return BaseClass::CanBeUsedAsAFriend(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_Vortigaunt::PrescheduleThink( void ) { if ( m_bGlowTurningOn ) { m_bGlowTurningOn = false; if ( m_pLeftHandGlow != NULL ) { m_pLeftHandGlow->SetScale( 0.5f, 2.0f ); m_pLeftHandGlow->SetBrightness( 200, 2.0f ); } if ( m_pRightHandGlow != NULL ) { m_pRightHandGlow->SetScale( 0.5f, 2.0f ); m_pRightHandGlow->SetBrightness( 200, 2.0f ); } } // See if we're able to heal now if ( ShouldHealTarget() ) { SetCondition( COND_VORTIGAUNT_CAN_HEAL ); } else { ClearCondition( COND_VORTIGAUNT_CAN_HEAL ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_Vortigaunt::BuildScheduleTestBits( void ) { BaseClass::BuildScheduleTestBits(); if ( IsCurSchedule( SCHED_IDLE_STAND ) || IsCurSchedule( SCHED_ALERT_STAND ) ) { SetCustomInterruptCondition( COND_VORTIGAUNT_CAN_HEAL ); } if ( ( ConditionInterruptsCurSchedule( COND_GIVE_WAY ) || IsCurSchedule(SCHED_HIDE_AND_RELOAD ) || IsCurSchedule(SCHED_RELOAD ) || IsCurSchedule(SCHED_STANDOFF ) || IsCurSchedule(SCHED_TAKE_COVER_FROM_ENEMY ) || IsCurSchedule(SCHED_COMBAT_FACE ) || IsCurSchedule(SCHED_ALERT_FACE ) || IsCurSchedule(SCHED_COMBAT_STAND ) || IsCurSchedule(SCHED_ALERT_FACE_BESTSOUND) || IsCurSchedule(SCHED_ALERT_STAND) ) ) { SetCustomInterruptCondition( COND_PLAYER_PUSHING ); } } //========================================================= // ArmBeam - small beam from arm to nearby geometry //========================================================= void CNPC_Vortigaunt::ArmBeam( int side, int beamType ) { trace_t tr; float flDist = 1.0; if (m_iBeams >= VORTIGAUNT_MAX_BEAMS) return; Vector forward, right, up; AngleVectors( GetLocalAngles(), &forward, &right, &up ); Vector vecSrc = GetLocalOrigin() + up * 36 + right * side * 16 + forward * 32; for (int i = 0; i < 3; i++) { Vector vecAim = forward * random->RandomFloat( -1, 1 ) + right * side * random->RandomFloat( 0, 1 ) + up * random->RandomFloat( -1, 1 ); trace_t tr1; AI_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; UTIL_ImpactTrace( &tr, DMG_CLUB ); m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/lgtning.vmt", 3.0 ); if (!m_pBeam[m_iBeams]) return; m_pBeam[m_iBeams]->PointEntInit( tr.endpos, this ); m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? m_iLeftHandAttachment : m_iRightHandAttachment ); if (beamType == VORTIGAUNT_BEAM_HEAL) { m_pBeam[m_iBeams]->SetColor( 0, 0, 255 ); } else { m_pBeam[m_iBeams]->SetColor( 96, 128, 16 ); } m_pBeam[m_iBeams]->SetBrightness( 64 ); m_pBeam[m_iBeams]->SetNoise( 12.5 ); m_iBeams++; } //------------------------------------------------------------------------------ // Purpose : Short beam on had in grenade defend mode // Input : // Output : //------------------------------------------------------------------------------ void CNPC_Vortigaunt::DefendBeams( void ) { Vector vBeamStart; // ----------- // Left hand // ----------- int i; GetAttachment( 1, vBeamStart ); for (i=0;i<4;i++) { Vector vBeamPos = vBeamStart; vBeamPos.x += random->RandomFloat(-8,8); vBeamPos.y += random->RandomFloat(-8,8); vBeamPos.z += random->RandomFloat(-8,8); CBeam *pBeam = CBeam::BeamCreate( "sprites/lgtning.vmt", 3.0 ); pBeam->PointEntInit( vBeamPos, this ); pBeam->SetEndAttachment( 1 ); pBeam->SetBrightness( 255 ); pBeam->SetNoise( 2 ); pBeam->SetColor( 96, 128, 16 ); pBeam->LiveForTime( 0.1 ); } // ----------- // Right hand // ----------- GetAttachment( 2, vBeamStart ); for (i=4;i<VORTIGAUNT_MAX_BEAMS;i++) { Vector vBeamPos = vBeamStart; vBeamPos.x += random->RandomFloat(-8,8); vBeamPos.y += random->RandomFloat(-8,8); vBeamPos.z += random->RandomFloat(-8,8); CBeam *pBeam = CBeam::BeamCreate( "sprites/lgtning.vmt", 3.0 ); pBeam->PointEntInit( vBeamPos, this ); pBeam->SetEndAttachment( 2 ); pBeam->SetBrightness( 255 ); pBeam->SetNoise( 2 ); pBeam->SetColor( 96, 128, 16 ); pBeam->LiveForTime( 0.1 ); } } //------------------------------------------------------------------------------ // Purpose : Glow for when vortigaunt punches manhack // Input : // Output : //------------------------------------------------------------------------------ void CNPC_Vortigaunt::ClawBeam( CBaseEntity *pHurt, int nNoise, int iAttachment ) { for (int i=0;i<2;i++) { CBeam* pBeam = CBeam::BeamCreate( "sprites/lgtning.vmt", 3.0 ); pBeam->EntsInit( this, pHurt ); pBeam->SetStartAttachment( iAttachment); pBeam->SetBrightness( 255 ); pBeam->SetNoise( nNoise ); pBeam->SetColor( 96, 128, 16 ); pBeam->LiveForTime( 0.2 ); } EmitSound( "NPC_Vortigaunt.ClawBeam" ); } //------------------------------------------------------------------------------ // Purpose : Put glowing sprites on hands // Input : // Output : //------------------------------------------------------------------------------ void CNPC_Vortigaunt::StartHandGlow( int beamType ) { m_bGlowTurningOn = true; m_fGlowAge = 0; if (beamType == VORTIGAUNT_BEAM_HEAL) { m_fGlowChangeTime = VORTIGAUNT_HEAL_GLOWGROW_TIME; m_fGlowScale = 0.6f; } else { m_fGlowChangeTime = VORTIGAUNT_ZAP_GLOWGROW_TIME; m_fGlowScale = 0.8f; } if ( m_pLeftHandGlow == NULL ) { if ( beamType == VORTIGAUNT_BEAM_HEAL ) { m_pLeftHandGlow = CSprite::SpriteCreate( "sprites/blueglow1.vmt", GetLocalOrigin(), FALSE ); m_pLeftHandGlow->SetAttachment( this, 3 ); } else { m_pLeftHandGlow = CSprite::SpriteCreate( "sprites/greenglow1.vmt", GetLocalOrigin(), FALSE ); m_pLeftHandGlow->SetAttachment( this, 3 ); } m_pLeftHandGlow->SetTransparency( kRenderGlow, 255, 200, 200, 0, kRenderFxNoDissipation ); m_pLeftHandGlow->SetBrightness( 0 ); m_pLeftHandGlow->SetScale( 0 ); } if ( beamType != VORTIGAUNT_BEAM_HEAL ) { if ( m_pRightHandGlow == NULL ) { m_pRightHandGlow = CSprite::SpriteCreate( "sprites/greenglow1.vmt", GetLocalOrigin(), FALSE ); m_pRightHandGlow->SetAttachment( this, 4 ); m_pRightHandGlow->SetTransparency( kRenderGlow, 255, 200, 200, 0, kRenderFxNoDissipation ); m_pRightHandGlow->SetBrightness( 0 ); m_pRightHandGlow->SetScale( 0 ); } } } //------------------------------------------------------------------------------ // Purpose : Fade glow from hands // Input : // Output : //------------------------------------------------------------------------------ void CNPC_Vortigaunt::EndHandGlow( void ) { m_bGlowTurningOn = false; m_fGlowChangeTime = VORTIGAUNT_GLOWFADE_TIME; if ( m_pLeftHandGlow ) { m_pLeftHandGlow->SetScale( 0, 0.5f ); m_pLeftHandGlow->FadeAndDie( 0.5f ); } if ( m_pRightHandGlow ) { m_pRightHandGlow->SetScale( 0, 0.5f ); m_pRightHandGlow->FadeAndDie( 0.5f ); } } //----------------------------------------------------------------------------- // Purpose: vortigaunt shoots at noisy body target (fixes problems in cover, etc) // NOTE: His weapon doesn't have any error // Input : // Output : //----------------------------------------------------------------------------- Vector CNPC_Vortigaunt::GetShootEnemyDir( const Vector &shootOrigin, bool bNoisy ) { CBaseEntity *pEnemy = GetEnemy(); if ( pEnemy ) { return (pEnemy->BodyTarget(shootOrigin, bNoisy)-shootOrigin); } else { Vector forward; AngleVectors( GetLocalAngles(), &forward ); return forward; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CNPC_Vortigaunt::IsValidEnemy( CBaseEntity *pEnemy ) { if ( IsRoller( pEnemy ) ) { CAI_BaseNPC *pNPC = pEnemy->MyNPCPointer(); if ( pNPC && pNPC->GetEnemy() != NULL ) return true; return false; } return BaseClass::IsValidEnemy( pEnemy ); } //========================================================= // 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]->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.0 ); if (!m_pBeam[m_iBeams]) return; m_pBeam[m_iBeams]->PointEntInit( pEntity->WorldSpaceCenter(), this ); m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? m_iLeftHandAttachment : m_iRightHandAttachment ); m_pBeam[m_iBeams]->SetColor( 180, 255, 96 ); m_pBeam[m_iBeams]->SetBrightness( 255 ); m_pBeam[m_iBeams]->SetNoise( 12 ); 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( GetLocalAngles(), &forward, &right, &up ); vecSrc = GetLocalOrigin() + up * 36; vecAim = GetShootEnemyDir( vecSrc ); float deflection = 0.01; vecAim = vecAim + side * right * random->RandomFloat( 0, deflection ) + up * random->RandomFloat( -deflection, deflection ); if ( m_bExtractingBugbait == true ) { CRagdollProp *pTest = dynamic_cast< CRagdollProp *>( GetTarget() ); if ( pTest ) { ragdoll_t *m_ragdoll = pTest->GetRagdoll(); if ( m_ragdoll ) { Vector vOrigin; m_ragdoll->list[0].pObject->GetPosition( &vOrigin, 0 ); AI_TraceLine ( vecSrc, vOrigin, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr); } CRagdollBoogie::Create( pTest, 200, gpGlobals->curtime, 1.0f ); } } else { AI_TraceLine ( vecSrc, vecSrc + vecAim * 1024, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr); } m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/lgtning.vmt", 5.0 ); if (!m_pBeam[m_iBeams]) return; m_pBeam[m_iBeams]->PointEntInit( tr.endpos, this ); m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? m_iLeftHandAttachment : m_iRightHandAttachment ); m_pBeam[m_iBeams]->SetColor( 180, 255, 96 ); m_pBeam[m_iBeams]->SetBrightness( 255 ); m_pBeam[m_iBeams]->SetNoise( 3 ); m_iBeams++; pEntity = tr.m_pEnt; if (pEntity != NULL && m_takedamage) { CTakeDamageInfo dmgInfo( this, this, sk_vortigaunt_dmg_zap.GetFloat(), DMG_SHOCK ); dmgInfo.SetDamagePosition( tr.endpos ); VectorNormalize( vecAim );// not a unit vec yet // hit like a 5kg object flying 400 ft/s dmgInfo.SetDamageForce( 5 * 400 * 12 * vecAim ); pEntity->DispatchTraceAttack( dmgInfo, vecAim, &tr ); } } //------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ void CNPC_Vortigaunt::HealBeam( int side ) { if (m_iBeams >= VORTIGAUNT_MAX_BEAMS) return; Vector forward, right, up; AngleVectors( GetLocalAngles(), &forward, &right, &up ); trace_t tr; Vector vecSrc = GetLocalOrigin() + up * 36; m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/lgtning.vmt", 5.0 ); if (!m_pBeam[m_iBeams]) return; m_pBeam[m_iBeams]->EntsInit( GetTarget(), this ); m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? m_iLeftHandAttachment : m_iRightHandAttachment ); m_pBeam[m_iBeams]->SetColor( 0, 0, 255 ); m_pBeam[m_iBeams]->SetBrightness( 255 ); m_pBeam[m_iBeams]->SetNoise( 6.5 ); m_iBeams++; } //------------------------------------------------------------------------------ // Purpose : Clear Glow // Input : // Output : //------------------------------------------------------------------------------ void CNPC_Vortigaunt::ClearHandGlow( ) { if (m_pLeftHandGlow) { UTIL_Remove( m_pLeftHandGlow ); m_pLeftHandGlow = NULL; } if (m_pRightHandGlow) { UTIL_Remove( m_pRightHandGlow ); m_pRightHandGlow = NULL; } } //========================================================= // 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; // Stop looping suit charge sound. if (m_iSuitSound > 1) { StopSound( "NPC_Vortigaunt.SuitCharge" ); m_iSuitSound = 0; } if ( m_bStopLoopingSounds ) { StopSound( "NPC_Vortigaunt.StartHealLoop" ); StopSound( "NPC_Vortigaunt.StartShootLoop" ); StopSound( "NPC_Vortigaunt.SuitCharge" ); StopSound( "NPC_Vortigaunt.Shoot" ); StopSound( "NPC_Vortigaunt.ZapPowerup" ); m_bStopLoopingSounds = false; } } //----------------------------------------------------------------------------- // Purpose: // Input : &data - //----------------------------------------------------------------------------- void CNPC_Vortigaunt::InputEnableArmorRecharge( inputdata_t &data ) { m_bArmorRechargeEnabled = true; } //----------------------------------------------------------------------------- // Purpose: // Input : &data - //----------------------------------------------------------------------------- void CNPC_Vortigaunt::InputDisableArmorRecharge( inputdata_t &data ) { m_bArmorRechargeEnabled = false; } //----------------------------------------------------------------------------- // Purpose: // Input : &data - //----------------------------------------------------------------------------- void CNPC_Vortigaunt::InputChargeTarget( inputdata_t &data ) { CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, data.value.String(), NULL, data.pActivator, data.pCaller ); // Must be valid if ( pTarget == NULL ) { DevMsg( 1, "Unable to charge from unknown entity: %s!\n", data.value.String() ); return; } int playerArmor = (pTarget->IsPlayer()) ? ((CBasePlayer *)pTarget)->ArmorValue() : 0; if ( playerArmor >= 100 || ( pTarget->GetFlags() & FL_NOTARGET ) ) { m_OnFinishedChargingTarget.FireOutput( this, this ); return; } m_hHealTarget = pTarget; m_iCurrentRechargeGoal = playerArmor + sk_vortigaunt_armor_charge.GetInt(); if ( m_iCurrentRechargeGoal > 100 ) m_iCurrentRechargeGoal = 100; m_bForceArmorRecharge = true; SetCondition( COND_PROVOKED ); } //----------------------------------------------------------------------------- // Purpose: // Input : &data - //----------------------------------------------------------------------------- void CNPC_Vortigaunt::InputExtractBugbait( inputdata_t &data ) { CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, data.value.String(), NULL, data.pActivator, data.pCaller ); // Must be valid if ( pTarget == NULL ) { DevMsg( 1, "Unable to extract bugbait from unknown entity %s!\n", data.value.String() ); return; } // Keep this as our target SetTarget( pTarget ); // Start to extract m_bExtractingBugbait = true; SetSchedule( SCHED_VORTIGAUNT_EXTRACT_BUGBAIT ); } //----------------------------------------------------------------------------- // The vort overloads the CNPC_PlayerCompanion version because he uses different // rules. The player companion rules looked too sensitive to muck with. //----------------------------------------------------------------------------- bool CNPC_Vortigaunt::OnObstructionPreSteer( AILocalMoveGoal_t *pMoveGoal, float distClear, AIMoveResult_t *pResult ) { if ( pMoveGoal->directTrace.flTotalDist - pMoveGoal->directTrace.flDistObstructed < GetHullWidth() * 1.5 ) { CAI_BaseNPC *pBlocker = pMoveGoal->directTrace.pObstruction->MyNPCPointer(); if ( pBlocker && pBlocker->IsPlayerAlly() && !pBlocker->IsMoving() && !pBlocker->IsInAScript() ) { if ( pBlocker->ConditionInterruptsCurSchedule( COND_GIVE_WAY ) || pBlocker->ConditionInterruptsCurSchedule( COND_PLAYER_PUSHING ) ) { // HACKHACK pBlocker->GetMotor()->SetIdealYawToTarget( WorldSpaceCenter() ); pBlocker->SetSchedule( SCHED_MOVE_AWAY ); } } } return BaseClass::OnObstructionPreSteer( pMoveGoal, distClear, pResult ); } //------------------------------------------------------------------------------ // // Schedules // //------------------------------------------------------------------------------ AI_BEGIN_CUSTOM_NPC( npc_vortigaunt, CNPC_Vortigaunt ) DECLARE_USES_SCHEDULE_PROVIDER( CAI_LeadBehavior ) DECLARE_TASK(TASK_VORTIGAUNT_HEAL_WARMUP) DECLARE_TASK(TASK_VORTIGAUNT_HEAL) DECLARE_TASK(TASK_VORTIGAUNT_FACE_STOMP) DECLARE_TASK(TASK_VORTIGAUNT_STOMP_ATTACK) DECLARE_TASK(TASK_VORTIGAUNT_GRENADE_KILL) DECLARE_TASK(TASK_VORTIGAUNT_ZAP_GRENADE_OWNER) DECLARE_TASK(TASK_VORTIGAUNT_EXTRACT) DECLARE_TASK(TASK_VORTIGAUNT_FIRE_EXTRACT_OUTPUT) DECLARE_TASK(TASK_VORTIGAUNT_WAIT_FOR_PLAYER) DECLARE_TASK( TASK_VORTIGAUNT_EXTRACT_WARMUP ) DECLARE_TASK( TASK_VORTIGAUNT_EXTRACT_COOLDOWN ) DECLARE_TASK( TASK_VORTIGAUNT_GET_HEAL_TARGET ) DECLARE_ACTIVITY(ACT_VORTIGAUNT_AIM) DECLARE_ACTIVITY(ACT_VORTIGAUNT_START_HEAL) DECLARE_ACTIVITY(ACT_VORTIGAUNT_HEAL_LOOP) DECLARE_ACTIVITY(ACT_VORTIGAUNT_END_HEAL) DECLARE_ACTIVITY(ACT_VORTIGAUNT_TO_ACTION) DECLARE_ACTIVITY(ACT_VORTIGAUNT_TO_IDLE) DECLARE_ACTIVITY(ACT_VORTIGAUNT_STOMP) DECLARE_ACTIVITY(ACT_VORTIGAUNT_DEFEND) DECLARE_ACTIVITY(ACT_VORTIGAUNT_TO_DEFEND) DECLARE_ACTIVITY(ACT_VORTIGAUNT_FROM_DEFEND) DECLARE_CONDITION(COND_VORTIGAUNT_CAN_HEAL) DECLARE_INTERACTION( g_interactionVortigauntStomp ) DECLARE_INTERACTION( g_interactionVortigauntStompFail ) DECLARE_INTERACTION( g_interactionVortigauntStompHit ) DECLARE_INTERACTION( g_interactionVortigauntKick ) DECLARE_INTERACTION( g_interactionVortigauntClaw ) DECLARE_ANIMEVENT( AE_VORTIGAUNT_CLAW_LEFT ) DECLARE_ANIMEVENT( AE_VORTIGAUNT_CLAW_RIGHT ) DECLARE_ANIMEVENT( AE_VORTIGAUNT_ZAP_POWERUP ) DECLARE_ANIMEVENT( AE_VORTIGAUNT_ZAP_SHOOT ) DECLARE_ANIMEVENT( AE_VORTIGAUNT_ZAP_DONE ) DECLARE_ANIMEVENT( AE_VORTIGAUNT_HEAL_STARTGLOW ) DECLARE_ANIMEVENT( AE_VORTIGAUNT_HEAL_STARTBEAMS ) DECLARE_ANIMEVENT( AE_VORTIGAUNT_KICK ) DECLARE_ANIMEVENT( AE_VORTIGAUNT_STOMP ) DECLARE_ANIMEVENT( AE_VORTIGAUNT_HEAL_STARTSOUND ) DECLARE_ANIMEVENT( AE_VORTIGAUNT_SWING_SOUND ) DECLARE_ANIMEVENT( AE_VORTIGAUNT_SHOOT_SOUNDSTART ) DECLARE_ANIMEVENT( AE_VORTIGAUNT_DEFEND_BEAMS ) //========================================================= // > SCHED_VORTIGAUNT_RANGE_ATTACK //========================================================= DEFINE_SCHEDULE ( SCHED_VORTIGAUNT_RANGE_ATTACK, " Tasks" " TASK_STOP_MOVING 0" " TASK_FACE_IDEAL 0" " TASK_RANGE_ATTACK1 0" " TASK_WAIT 0.2" // Wait a sec before killing beams "" " Interrupts" ); //========================================================= // > SCHED_VORTIGAUNT_MELEE_ATTACK //========================================================= DEFINE_SCHEDULE ( SCHED_VORTIGAUNT_MELEE_ATTACK, " Tasks" " TASK_STOP_MOVING 0" " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" " TASK_FACE_ENEMY 0" " TASK_MELEE_ATTACK1 0" "" " Interrupts" " COND_ENEMY_DEAD" " COND_HEAVY_DAMAGE" " COND_ENEMY_OCCLUDED" ); //========================================================= // > SCHED_VORTIGAUNT_STOMP_ATTACK //========================================================= DEFINE_SCHEDULE ( SCHED_VORTIGAUNT_STOMP_ATTACK, " Tasks" " TASK_STOP_MOVING 0" " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" " TASK_VORTIGAUNT_FACE_STOMP 0" " TASK_VORTIGAUNT_STOMP_ATTACK 0" "" " Interrupts" // New_Enemy Don't interrupt, finish current attack first " COND_ENEMY_DEAD" " COND_HEAVY_DAMAGE" " COND_ENEMY_OCCLUDED" ); //========================================================= // > SCHED_VORTIGAUNT_GRENADE_KILL // // Zap an incoming grenade (GetTarget()) //========================================================= DEFINE_SCHEDULE ( SCHED_VORTIGAUNT_GRENADE_KILL, " Tasks" " TASK_VORTIGAUNT_GRENADE_KILL 0" " TASK_WAIT 0.3" " TASK_VORTIGAUNT_ZAP_GRENADE_OWNER 0" "" " Interrupts" " COND_LIGHT_DAMAGE" ); //========================================================= // > SCHED_VORTIGAUNT_HEAL //========================================================= DEFINE_SCHEDULE ( SCHED_VORTIGAUNT_HEAL, " Tasks" " TASK_VORTIGAUNT_GET_HEAL_TARGET 0" " TASK_STOP_MOVING 0" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_VORTIGAUNT_STAND" " TASK_GET_PATH_TO_TARGET 0" " TASK_MOVE_TO_TARGET_RANGE 128" // Move within 128 of target ent (client) " TASK_STOP_MOVING 0" " TASK_FACE_PLAYER 0" " TASK_SPEAK_SENTENCE 0" // VORT_HEAL_SENTENCE " TASK_VORTIGAUNT_HEAL_WARMUP 0" " TASK_VORTIGAUNT_HEAL 0" " TASK_SPEAK_SENTENCE 1" // VORT_DONE_HEAL_SENTENCE "" " Interrupts" " COND_LIGHT_DAMAGE" " COND_HEAVY_DAMAGE" ); //========================================================= // > SCHED_VORTIGAUNT_STAND //========================================================= DEFINE_SCHEDULE ( SCHED_VORTIGAUNT_STAND, " Tasks" " TASK_STOP_MOVING 0" " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" " TASK_WAIT 2" // repick IDLESTAND every two seconds." " TASK_TALKER_HEADRESET 0" // reset head position" "" " Interrupts" " COND_NEW_ENEMY" " COND_LIGHT_DAMAGE" " COND_HEAVY_DAMAGE" " COND_SMELL" " COND_PROVOKED" " COND_HEAR_COMBAT" " COND_HEAR_DANGER" ); //========================================================= // > SCHED_VORTIGAUNT_BARNACLE_HIT //========================================================= DEFINE_SCHEDULE ( SCHED_VORTIGAUNT_BARNACLE_HIT, " Tasks" " TASK_STOP_MOVING 0" " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_HIT" " TASK_SET_SCHEDULE SCHEDULE:SCHED_VORTIGAUNT_BARNACLE_PULL" "" " Interrupts" ); //========================================================= // > SCHED_VORTIGAUNT_BARNACLE_PULL //========================================================= DEFINE_SCHEDULE ( SCHED_VORTIGAUNT_BARNACLE_PULL, " Tasks" " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_PULL" "" " Interrupts" ); //========================================================= // > SCHED_VORTIGAUNT_BARNACLE_CHOMP //========================================================= DEFINE_SCHEDULE ( SCHED_VORTIGAUNT_BARNACLE_CHOMP, " Tasks" " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_CHOMP" " TASK_SET_SCHEDULE SCHEDULE:SCHED_VORTIGAUNT_BARNACLE_CHEW" "" " Interrupts" ); //========================================================= // > SCHED_VORTIGAUNT_BARNACLE_CHEW //========================================================= DEFINE_SCHEDULE ( SCHED_VORTIGAUNT_BARNACLE_CHEW, " Tasks" " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_CHEW" ) //========================================================= // > SCHED_VORTIGAUNT_EXTRACT_BUGBAIT //========================================================= DEFINE_SCHEDULE ( SCHED_VORTIGAUNT_EXTRACT_BUGBAIT, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_VORTIGAUNT_STAND" " TASK_STOP_MOVING 0" " TASK_GET_PATH_TO_TARGET 0" " TASK_MOVE_TO_TARGET_RANGE 128" // Move within 128 of target ent (client) " TASK_STOP_MOVING 0" " TASK_VORTIGAUNT_WAIT_FOR_PLAYER 0" " TASK_SPEAK_SENTENCE 2" // Start extracting sentence " TASK_WAIT_FOR_SPEAK_FINISH 1" " TASK_FACE_TARGET 0" " TASK_WAIT_FOR_SPEAK_FINISH 1" " TASK_VORTIGAUNT_EXTRACT_WARMUP 0" " TASK_VORTIGAUNT_EXTRACT 0" " TASK_VORTIGAUNT_EXTRACT_COOLDOWN 0" " TASK_VORTIGAUNT_FIRE_EXTRACT_OUTPUT 0" " TASK_SPEAK_SENTENCE 3" // Finish extracting sentence " TASK_WAIT_FOR_SPEAK_FINISH 1" " TASK_WAIT 2" "" " Interrupts" ) AI_END_CUSTOM_NPC()
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 5821 | Knut Wikstrom |
Added Valve Source code. This is NOT to be commited to other than new code from Valve. |