diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /sp/src/game/server/hl2/npc_combine.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'sp/src/game/server/hl2/npc_combine.cpp')
| -rw-r--r-- | sp/src/game/server/hl2/npc_combine.cpp | 8090 |
1 files changed, 4045 insertions, 4045 deletions
diff --git a/sp/src/game/server/hl2/npc_combine.cpp b/sp/src/game/server/hl2/npc_combine.cpp index ea8f6aa6..09cae88a 100644 --- a/sp/src/game/server/hl2/npc_combine.cpp +++ b/sp/src/game/server/hl2/npc_combine.cpp @@ -1,4045 +1,4045 @@ -//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================//
-
-#include "cbase.h"
-#include "ai_hull.h"
-#include "ai_navigator.h"
-#include "ai_motor.h"
-#include "ai_squadslot.h"
-#include "ai_squad.h"
-#include "ai_route.h"
-#include "ai_interactions.h"
-#include "ai_tacticalservices.h"
-#include "soundent.h"
-#include "game.h"
-#include "npcevent.h"
-#include "npc_combine.h"
-#include "activitylist.h"
-#include "player.h"
-#include "basecombatweapon.h"
-#include "basegrenade_shared.h"
-#include "vstdlib/random.h"
-#include "engine/IEngineSound.h"
-#include "globals.h"
-#include "grenade_frag.h"
-#include "ndebugoverlay.h"
-#include "weapon_physcannon.h"
-#include "SoundEmitterSystem/isoundemittersystembase.h"
-#include "npc_headcrab.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-int g_fCombineQuestion; // true if an idle grunt asked a question. Cleared when someone answers. YUCK old global from grunt code
-
-#define COMBINE_SKIN_DEFAULT 0
-#define COMBINE_SKIN_SHOTGUNNER 1
-
-
-#define COMBINE_GRENADE_THROW_SPEED 650
-#define COMBINE_GRENADE_TIMER 3.5
-#define COMBINE_GRENADE_FLUSH_TIME 3.0 // Don't try to flush an enemy who has been out of sight for longer than this.
-#define COMBINE_GRENADE_FLUSH_DIST 256.0 // Don't try to flush an enemy who has moved farther than this distance from the last place I saw him.
-
-#define COMBINE_LIMP_HEALTH 20
-#define COMBINE_MIN_GRENADE_CLEAR_DIST 250
-
-#define COMBINE_EYE_STANDING_POSITION Vector( 0, 0, 66 )
-#define COMBINE_GUN_STANDING_POSITION Vector( 0, 0, 57 )
-#define COMBINE_EYE_CROUCHING_POSITION Vector( 0, 0, 40 )
-#define COMBINE_GUN_CROUCHING_POSITION Vector( 0, 0, 36 )
-#define COMBINE_SHOTGUN_STANDING_POSITION Vector( 0, 0, 36 )
-#define COMBINE_SHOTGUN_CROUCHING_POSITION Vector( 0, 0, 36 )
-#define COMBINE_MIN_CROUCH_DISTANCE 256.0
-
-//-----------------------------------------------------------------------------
-// Static stuff local to this file.
-//-----------------------------------------------------------------------------
-// This is the index to the name of the shotgun's classname in the string pool
-// so that we can get away with an integer compare rather than a string compare.
-string_t s_iszShotgunClassname;
-
-//-----------------------------------------------------------------------------
-// Interactions
-//-----------------------------------------------------------------------------
-int g_interactionCombineBash = 0; // melee bash attack
-
-//=========================================================
-// Combines's Anim Events Go Here
-//=========================================================
-#define COMBINE_AE_RELOAD ( 2 )
-#define COMBINE_AE_KICK ( 3 )
-#define COMBINE_AE_AIM ( 4 )
-#define COMBINE_AE_GREN_TOSS ( 7 )
-#define COMBINE_AE_GREN_LAUNCH ( 8 )
-#define COMBINE_AE_GREN_DROP ( 9 )
-#define COMBINE_AE_CAUGHT_ENEMY ( 10) // grunt established sight with an enemy (player only) that had previously eluded the squad.
-
-int COMBINE_AE_BEGIN_ALTFIRE;
-int COMBINE_AE_ALTFIRE;
-
-//=========================================================
-// Combine activities
-//=========================================================
-//Activity ACT_COMBINE_STANDING_SMG1;
-//Activity ACT_COMBINE_CROUCHING_SMG1;
-//Activity ACT_COMBINE_STANDING_AR2;
-//Activity ACT_COMBINE_CROUCHING_AR2;
-//Activity ACT_COMBINE_WALKING_AR2;
-//Activity ACT_COMBINE_STANDING_SHOTGUN;
-//Activity ACT_COMBINE_CROUCHING_SHOTGUN;
-Activity ACT_COMBINE_THROW_GRENADE;
-Activity ACT_COMBINE_LAUNCH_GRENADE;
-Activity ACT_COMBINE_BUGBAIT;
-Activity ACT_COMBINE_AR2_ALTFIRE;
-Activity ACT_WALK_EASY;
-Activity ACT_WALK_MARCH;
-
-// -----------------------------------------------
-// > Squad slots
-// -----------------------------------------------
-enum SquadSlot_T
-{
- SQUAD_SLOT_GRENADE1 = LAST_SHARED_SQUADSLOT,
- SQUAD_SLOT_GRENADE2,
- SQUAD_SLOT_ATTACK_OCCLUDER,
- SQUAD_SLOT_OVERWATCH,
-};
-
-enum TacticalVariant_T
-{
- TACTICAL_VARIANT_DEFAULT = 0,
- TACTICAL_VARIANT_PRESSURE_ENEMY, // Always try to close in on the player.
- TACTICAL_VARIANT_PRESSURE_ENEMY_UNTIL_CLOSE, // Act like VARIANT_PRESSURE_ENEMY, but go to VARIANT_DEFAULT once within 30 feet
-};
-
-enum PathfindingVariant_T
-{
- PATHFINDING_VARIANT_DEFAULT = 0,
-};
-
-
-#define bits_MEMORY_PAIN_LIGHT_SOUND bits_MEMORY_CUSTOM1
-#define bits_MEMORY_PAIN_HEAVY_SOUND bits_MEMORY_CUSTOM2
-#define bits_MEMORY_PLAYER_HURT bits_MEMORY_CUSTOM3
-
-LINK_ENTITY_TO_CLASS( npc_combine, CNPC_Combine );
-
-//---------------------------------------------------------
-// Save/Restore
-//---------------------------------------------------------
-BEGIN_DATADESC( CNPC_Combine )
-
-DEFINE_FIELD( m_nKickDamage, FIELD_INTEGER ),
-DEFINE_FIELD( m_vecTossVelocity, FIELD_VECTOR ),
-DEFINE_FIELD( m_hForcedGrenadeTarget, FIELD_EHANDLE ),
-DEFINE_FIELD( m_bShouldPatrol, FIELD_BOOLEAN ),
-DEFINE_FIELD( m_bFirstEncounter, FIELD_BOOLEAN ),
-DEFINE_FIELD( m_flNextPainSoundTime, FIELD_TIME ),
-DEFINE_FIELD( m_flNextAlertSoundTime, FIELD_TIME ),
-DEFINE_FIELD( m_flNextGrenadeCheck, FIELD_TIME ),
-DEFINE_FIELD( m_flNextLostSoundTime, FIELD_TIME ),
-DEFINE_FIELD( m_flAlertPatrolTime, FIELD_TIME ),
-DEFINE_FIELD( m_flNextAltFireTime, FIELD_TIME ),
-DEFINE_FIELD( m_nShots, FIELD_INTEGER ),
-DEFINE_FIELD( m_flShotDelay, FIELD_FLOAT ),
-DEFINE_FIELD( m_flStopMoveShootTime, FIELD_TIME ),
-DEFINE_KEYFIELD( m_iNumGrenades, FIELD_INTEGER, "NumGrenades" ),
-DEFINE_EMBEDDED( m_Sentences ),
-
-// m_AssaultBehavior (auto saved by AI)
-// m_StandoffBehavior (auto saved by AI)
-// m_FollowBehavior (auto saved by AI)
-// m_FuncTankBehavior (auto saved by AI)
-// m_RappelBehavior (auto saved by AI)
-// m_ActBusyBehavior (auto saved by AI)
-
-DEFINE_INPUTFUNC( FIELD_VOID, "LookOff", InputLookOff ),
-DEFINE_INPUTFUNC( FIELD_VOID, "LookOn", InputLookOn ),
-
-DEFINE_INPUTFUNC( FIELD_VOID, "StartPatrolling", InputStartPatrolling ),
-DEFINE_INPUTFUNC( FIELD_VOID, "StopPatrolling", InputStopPatrolling ),
-
-DEFINE_INPUTFUNC( FIELD_STRING, "Assault", InputAssault ),
-
-DEFINE_INPUTFUNC( FIELD_VOID, "HitByBugbait", InputHitByBugbait ),
-
-DEFINE_INPUTFUNC( FIELD_STRING, "ThrowGrenadeAtTarget", InputThrowGrenadeAtTarget ),
-
-DEFINE_FIELD( m_iLastAnimEventHandled, FIELD_INTEGER ),
-DEFINE_FIELD( m_fIsElite, FIELD_BOOLEAN ),
-DEFINE_FIELD( m_vecAltFireTarget, FIELD_VECTOR ),
-
-DEFINE_KEYFIELD( m_iTacticalVariant, FIELD_INTEGER, "tacticalvariant" ),
-DEFINE_KEYFIELD( m_iPathfindingVariant, FIELD_INTEGER, "pathfindingvariant" ),
-
-END_DATADESC()
-
-
-//------------------------------------------------------------------------------
-// Constructor.
-//------------------------------------------------------------------------------
-CNPC_Combine::CNPC_Combine()
-{
- m_vecTossVelocity = vec3_origin;
-}
-
-
-//-----------------------------------------------------------------------------
-// Create components
-//-----------------------------------------------------------------------------
-bool CNPC_Combine::CreateComponents()
-{
- if ( !BaseClass::CreateComponents() )
- return false;
-
- m_Sentences.Init( this, "NPC_Combine.SentenceParameters" );
- return true;
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose: Don't look, only get info from squad.
-//------------------------------------------------------------------------------
-void CNPC_Combine::InputLookOff( inputdata_t &inputdata )
-{
- m_spawnflags |= SF_COMBINE_NO_LOOK;
-}
-
-//------------------------------------------------------------------------------
-// Purpose: Enable looking.
-//------------------------------------------------------------------------------
-void CNPC_Combine::InputLookOn( inputdata_t &inputdata )
-{
- m_spawnflags &= ~SF_COMBINE_NO_LOOK;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Combine::InputStartPatrolling( inputdata_t &inputdata )
-{
- m_bShouldPatrol = true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Combine::InputStopPatrolling( inputdata_t &inputdata )
-{
- m_bShouldPatrol = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Combine::InputAssault( inputdata_t &inputdata )
-{
- m_AssaultBehavior.SetParameters( AllocPooledString(inputdata.value.String()), CUE_DONT_WAIT, RALLY_POINT_SELECT_DEFAULT );
-}
-
-//-----------------------------------------------------------------------------
-// We were hit by bugbait
-//-----------------------------------------------------------------------------
-void CNPC_Combine::InputHitByBugbait( inputdata_t &inputdata )
-{
- SetCondition( COND_COMBINE_HIT_BY_BUGBAIT );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Force the combine soldier to throw a grenade at the target
-// If I'm a combine elite, fire my combine ball at the target instead.
-// Input : &inputdata -
-//-----------------------------------------------------------------------------
-void CNPC_Combine::InputThrowGrenadeAtTarget( inputdata_t &inputdata )
-{
- // Ignore if we're inside a scripted sequence
- if ( m_NPCState == NPC_STATE_SCRIPT && m_hCine )
- return;
-
- CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.String(), NULL, inputdata.pActivator, inputdata.pCaller );
- if ( !pEntity )
- {
- DevMsg("%s (%s) received ThrowGrenadeAtTarget input, but couldn't find target entity '%s'\n", GetClassname(), GetDebugName(), inputdata.value.String() );
- return;
- }
-
- m_hForcedGrenadeTarget = pEntity;
- m_flNextGrenadeCheck = 0;
-
- ClearSchedule( "Told to throw grenade via input" );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Combine::Precache()
-{
- PrecacheModel("models/Weapons/w_grenade.mdl");
- UTIL_PrecacheOther( "npc_handgrenade" );
-
- PrecacheScriptSound( "NPC_Combine.GrenadeLaunch" );
- PrecacheScriptSound( "NPC_Combine.WeaponBash" );
- PrecacheScriptSound( "Weapon_CombineGuard.Special1" );
-
- BaseClass::Precache();
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Combine::Activate()
-{
- s_iszShotgunClassname = FindPooledString( "weapon_shotgun" );
- BaseClass::Activate();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//
-//
-//-----------------------------------------------------------------------------
-void CNPC_Combine::Spawn( void )
-{
- SetHullType(HULL_HUMAN);
- SetHullSizeNormal();
-
- SetSolid( SOLID_BBOX );
- AddSolidFlags( FSOLID_NOT_STANDABLE );
- SetMoveType( MOVETYPE_STEP );
- SetBloodColor( BLOOD_COLOR_RED );
- m_flFieldOfView = -0.2;// indicates the width of this NPC's forward view cone ( as a dotproduct result )
- m_NPCState = NPC_STATE_NONE;
- m_flNextGrenadeCheck = gpGlobals->curtime + 1;
- m_flNextPainSoundTime = 0;
- m_flNextAlertSoundTime = 0;
- m_bShouldPatrol = false;
-
- // CapabilitiesAdd( bits_CAP_TURN_HEAD | bits_CAP_MOVE_GROUND | bits_CAP_MOVE_JUMP | bits_CAP_MOVE_CLIMB);
- // JAY: Disabled jump for now - hard to compare to HL1
- CapabilitiesAdd( bits_CAP_TURN_HEAD | bits_CAP_MOVE_GROUND );
-
- CapabilitiesAdd( bits_CAP_AIM_GUN );
-
- // Innate range attack for grenade
- // CapabilitiesAdd(bits_CAP_INNATE_RANGE_ATTACK2 );
-
- // Innate range attack for kicking
- CapabilitiesAdd(bits_CAP_INNATE_MELEE_ATTACK1 );
-
- // Can be in a squad
- CapabilitiesAdd( bits_CAP_SQUAD);
- CapabilitiesAdd( bits_CAP_USE_WEAPONS );
-
- CapabilitiesAdd( bits_CAP_DUCK ); // In reloading and cover
-
- CapabilitiesAdd( bits_CAP_NO_HIT_SQUADMATES );
-
- m_bFirstEncounter = true;// this is true when the grunt spawns, because he hasn't encountered an enemy yet.
-
- m_HackedGunPos = Vector ( 0, 0, 55 );
-
- m_flStopMoveShootTime = FLT_MAX; // Move and shoot defaults on.
- m_MoveAndShootOverlay.SetInitialDelay( 0.75 ); // But with a bit of a delay.
-
- m_flNextLostSoundTime = 0;
- m_flAlertPatrolTime = 0;
-
- m_flNextAltFireTime = gpGlobals->curtime;
-
- NPCInit();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CNPC_Combine::CreateBehaviors()
-{
- AddBehavior( &m_RappelBehavior );
- AddBehavior( &m_ActBusyBehavior );
- AddBehavior( &m_AssaultBehavior );
- AddBehavior( &m_StandoffBehavior );
- AddBehavior( &m_FollowBehavior );
- AddBehavior( &m_FuncTankBehavior );
-
- return BaseClass::CreateBehaviors();
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Combine::PostNPCInit()
-{
- if( IsElite() )
- {
- // Give a warning if a Combine Soldier is equipped with anything other than
- // an AR2.
- if( !GetActiveWeapon() || !FClassnameIs( GetActiveWeapon(), "weapon_ar2" ) )
- {
- DevWarning("**Combine Elite Soldier MUST be equipped with AR2\n");
- }
- }
-
- BaseClass::PostNPCInit();
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Combine::GatherConditions()
-{
- BaseClass::GatherConditions();
-
- ClearCondition( COND_COMBINE_ATTACK_SLOT_AVAILABLE );
-
- if( GetState() == NPC_STATE_COMBAT )
- {
- if( IsCurSchedule( SCHED_COMBINE_WAIT_IN_COVER, false ) )
- {
- // Soldiers that are standing around doing nothing poll for attack slots so
- // that they can respond quickly when one comes available. If they can
- // occupy a vacant attack slot, they do so. This holds the slot until their
- // schedule breaks and schedule selection runs again, essentially reserving this
- // slot. If they do not select an attack schedule, then they'll release the slot.
- if( OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
- {
- SetCondition( COND_COMBINE_ATTACK_SLOT_AVAILABLE );
- }
- }
-
- if( IsUsingTacticalVariant(TACTICAL_VARIANT_PRESSURE_ENEMY_UNTIL_CLOSE) )
- {
- if( GetEnemy() != NULL && !HasCondition(COND_ENEMY_OCCLUDED) )
- {
- // Now we're close to our enemy, stop using the tactical variant.
- if( GetAbsOrigin().DistToSqr(GetEnemy()->GetAbsOrigin()) < Square(30.0f * 12.0f) )
- m_iTacticalVariant = TACTICAL_VARIANT_DEFAULT;
- }
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Combine::PrescheduleThink()
-{
- BaseClass::PrescheduleThink();
-
- // Speak any queued sentences
- m_Sentences.UpdateSentenceQueue();
-
- if ( IsOnFire() )
- {
- SetCondition( COND_COMBINE_ON_FIRE );
- }
- else
- {
- ClearCondition( COND_COMBINE_ON_FIRE );
- }
-
- extern ConVar ai_debug_shoot_positions;
- if ( ai_debug_shoot_positions.GetBool() )
- NDebugOverlay::Cross3D( EyePosition(), 16, 0, 255, 0, false, 0.1 );
-
- if( gpGlobals->curtime >= m_flStopMoveShootTime )
- {
- // Time to stop move and shoot and start facing the way I'm running.
- // This makes the combine look attentive when disengaging, but prevents
- // them from always running around facing you.
- //
- // Only do this if it won't be immediately shut off again.
- if( GetNavigator()->GetPathTimeToGoal() > 1.0f )
- {
- m_MoveAndShootOverlay.SuspendMoveAndShoot( 5.0f );
- m_flStopMoveShootTime = FLT_MAX;
- }
- }
-
- if( m_flGroundSpeed > 0 && GetState() == NPC_STATE_COMBAT && m_MoveAndShootOverlay.IsSuspended() )
- {
- // Return to move and shoot when near my goal so that I 'tuck into' the location facing my enemy.
- if( GetNavigator()->GetPathTimeToGoal() <= 1.0f )
- {
- m_MoveAndShootOverlay.SuspendMoveAndShoot( 0 );
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Combine::DelayAltFireAttack( float flDelay )
-{
- float flNextAltFire = gpGlobals->curtime + flDelay;
-
- if( flNextAltFire > m_flNextAltFireTime )
- {
- // Don't let this delay order preempt a previous request to wait longer.
- m_flNextAltFireTime = flNextAltFire;
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Combine::DelaySquadAltFireAttack( float flDelay )
-{
- // Make sure to delay my own alt-fire attack.
- DelayAltFireAttack( flDelay );
-
- AISquadIter_t iter;
- CAI_BaseNPC *pSquadmate = m_pSquad ? m_pSquad->GetFirstMember( &iter ) : NULL;
- while ( pSquadmate )
- {
- CNPC_Combine *pCombine = dynamic_cast<CNPC_Combine*>(pSquadmate);
-
- if( pCombine && pCombine->IsElite() )
- {
- pCombine->DelayAltFireAttack( flDelay );
- }
-
- pSquadmate = m_pSquad->GetNextMember( &iter );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: degrees to turn in 0.1 seconds
-//-----------------------------------------------------------------------------
-float CNPC_Combine::MaxYawSpeed( void )
-{
- switch( GetActivity() )
- {
- case ACT_TURN_LEFT:
- case ACT_TURN_RIGHT:
- return 45;
- break;
- case ACT_RUN:
- case ACT_RUN_HURT:
- return 15;
- break;
- case ACT_WALK:
- case ACT_WALK_CROUCH:
- return 25;
- break;
- case ACT_RANGE_ATTACK1:
- case ACT_RANGE_ATTACK2:
- case ACT_MELEE_ATTACK1:
- case ACT_MELEE_ATTACK2:
- return 35;
- default:
- return 35;
- break;
- }
-}
-
-//-----------------------------------------------------------------------------
-//
-//-----------------------------------------------------------------------------
-bool CNPC_Combine::ShouldMoveAndShoot()
-{
- // Set this timer so that gpGlobals->curtime can't catch up to it.
- // Essentially, we're saying that we're not going to interfere with
- // what the AI wants to do with move and shoot.
- //
- // If any code below changes this timer, the code is saying
- // "It's OK to move and shoot until gpGlobals->curtime == m_flStopMoveShootTime"
- m_flStopMoveShootTime = FLT_MAX;
-
- if( IsCurSchedule( SCHED_COMBINE_HIDE_AND_RELOAD, false ) )
- m_flStopMoveShootTime = gpGlobals->curtime + random->RandomFloat( 0.4f, 0.6f );
-
- if( IsCurSchedule( SCHED_TAKE_COVER_FROM_BEST_SOUND, false ) )
- return false;
-
- if( IsCurSchedule( SCHED_COMBINE_TAKE_COVER_FROM_BEST_SOUND, false ) )
- return false;
-
- if( IsCurSchedule( SCHED_COMBINE_RUN_AWAY_FROM_BEST_SOUND, false ) )
- return false;
-
- if( HasCondition( COND_NO_PRIMARY_AMMO, false ) )
- m_flStopMoveShootTime = gpGlobals->curtime + random->RandomFloat( 0.4f, 0.6f );
-
- if( m_pSquad && IsCurSchedule( SCHED_COMBINE_TAKE_COVER1, false ) )
- m_flStopMoveShootTime = gpGlobals->curtime + random->RandomFloat( 0.4f, 0.6f );
-
- return BaseClass::ShouldMoveAndShoot();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: turn in the direction of movement
-// Output :
-//-----------------------------------------------------------------------------
-bool CNPC_Combine::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval )
-{
- return BaseClass::OverrideMoveFacing( move, flInterval );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//
-//
-//-----------------------------------------------------------------------------
-Class_T CNPC_Combine::Classify ( void )
-{
- return CLASS_COMBINE;
-}
-
-
-//-----------------------------------------------------------------------------
-// Continuous movement tasks
-//-----------------------------------------------------------------------------
-bool CNPC_Combine::IsCurTaskContinuousMove()
-{
- const Task_t* pTask = GetTask();
- if ( pTask && (pTask->iTask == TASK_COMBINE_CHASE_ENEMY_CONTINUOUSLY) )
- return true;
-
- return BaseClass::IsCurTaskContinuousMove();
-}
-
-
-//-----------------------------------------------------------------------------
-// Chase the enemy, updating the target position as the player moves
-//-----------------------------------------------------------------------------
-void CNPC_Combine::StartTaskChaseEnemyContinuously( const Task_t *pTask )
-{
- CBaseEntity *pEnemy = GetEnemy();
- if ( !pEnemy )
- {
- TaskFail( FAIL_NO_ENEMY );
- return;
- }
-
- // We're done once we get close enough
- if ( WorldSpaceCenter().DistToSqr( pEnemy->WorldSpaceCenter() ) <= pTask->flTaskData * pTask->flTaskData )
- {
- TaskComplete();
- return;
- }
-
- // TASK_GET_PATH_TO_ENEMY
- if ( IsUnreachable( pEnemy ) )
- {
- TaskFail(FAIL_NO_ROUTE);
- return;
- }
-
- if ( !GetNavigator()->SetGoal( GOALTYPE_ENEMY, AIN_NO_PATH_TASK_FAIL ) )
- {
- // no way to get there =(
- DevWarning( 2, "GetPathToEnemy failed!!\n" );
- RememberUnreachable( pEnemy );
- TaskFail(FAIL_NO_ROUTE);
- return;
- }
-
- // NOTE: This is TaskRunPath here.
- if ( TranslateActivity( ACT_RUN ) != ACT_INVALID )
- {
- GetNavigator()->SetMovementActivity( ACT_RUN );
- }
- else
- {
- GetNavigator()->SetMovementActivity(ACT_WALK);
- }
-
- // Cover is void once I move
- Forget( bits_MEMORY_INCOVER );
-
- if (GetNavigator()->GetGoalType() == GOALTYPE_NONE)
- {
- TaskComplete();
- GetNavigator()->ClearGoal(); // Clear residual state
- return;
- }
-
- // No shooting delay when in this mode
- m_MoveAndShootOverlay.SetInitialDelay( 0.0 );
-
- if (!GetNavigator()->IsGoalActive())
- {
- SetIdealActivity( GetStoppedActivity() );
- }
- else
- {
- // Check validity of goal type
- ValidateNavGoal();
- }
-
- // set that we're probably going to stop before the goal
- GetNavigator()->SetArrivalDistance( pTask->flTaskData );
- m_vSavePosition = GetEnemy()->WorldSpaceCenter();
-}
-
-void CNPC_Combine::RunTaskChaseEnemyContinuously( const Task_t *pTask )
-{
- if (!GetNavigator()->IsGoalActive())
- {
- SetIdealActivity( GetStoppedActivity() );
- }
- else
- {
- // Check validity of goal type
- ValidateNavGoal();
- }
-
- CBaseEntity *pEnemy = GetEnemy();
- if ( !pEnemy )
- {
- TaskFail( FAIL_NO_ENEMY );
- return;
- }
-
- // We're done once we get close enough
- if ( WorldSpaceCenter().DistToSqr( pEnemy->WorldSpaceCenter() ) <= pTask->flTaskData * pTask->flTaskData )
- {
- GetNavigator()->StopMoving();
- TaskComplete();
- return;
- }
-
- // Recompute path if the enemy has moved too much
- if ( m_vSavePosition.DistToSqr( pEnemy->WorldSpaceCenter() ) < (pTask->flTaskData * pTask->flTaskData) )
- return;
-
- if ( IsUnreachable( pEnemy ) )
- {
- TaskFail(FAIL_NO_ROUTE);
- return;
- }
-
- if ( !GetNavigator()->RefindPathToGoal() )
- {
- TaskFail(FAIL_NO_ROUTE);
- return;
- }
-
- m_vSavePosition = pEnemy->WorldSpaceCenter();
-}
-
-
-//=========================================================
-// start task
-//=========================================================
-void CNPC_Combine::StartTask( const Task_t *pTask )
-{
- // NOTE: This reset is required because we change it in TASK_COMBINE_CHASE_ENEMY_CONTINUOUSLY
- m_MoveAndShootOverlay.SetInitialDelay( 0.75 );
-
- switch ( pTask->iTask )
- {
- case TASK_COMBINE_SET_STANDING:
- {
- if ( pTask->flTaskData == 1.0f)
- {
- Stand();
- }
- else
- {
- Crouch();
- }
- TaskComplete();
- }
- break;
-
- case TASK_COMBINE_CHASE_ENEMY_CONTINUOUSLY:
- StartTaskChaseEnemyContinuously( pTask );
- break;
-
- case TASK_COMBINE_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET:
- SetIdealActivity( (Activity)(int)pTask->flTaskData );
- GetMotor()->SetIdealYawToTargetAndUpdate( m_vecAltFireTarget, AI_KEEP_YAW_SPEED );
- break;
-
- case TASK_COMBINE_SIGNAL_BEST_SOUND:
- if( IsInSquad() && GetSquad()->NumMembers() > 1 )
- {
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
-
- if( pPlayer && OccupyStrategySlot( SQUAD_SLOT_EXCLUSIVE_HANDSIGN ) && pPlayer->FInViewCone( this ) )
- {
- CSound *pSound;
- pSound = GetBestSound();
-
- Assert( pSound != NULL );
-
- if ( pSound )
- {
- Vector right, tosound;
-
- GetVectors( NULL, &right, NULL );
-
- tosound = pSound->GetSoundReactOrigin() - GetAbsOrigin();
- VectorNormalize( tosound);
-
- tosound.z = 0;
- right.z = 0;
-
- if( DotProduct( right, tosound ) > 0 )
- {
- // Right
- SetIdealActivity( ACT_SIGNAL_RIGHT );
- }
- else
- {
- // Left
- SetIdealActivity( ACT_SIGNAL_LEFT );
- }
-
- break;
- }
- }
- }
-
- // Otherwise, just skip it.
- TaskComplete();
- break;
-
- case TASK_ANNOUNCE_ATTACK:
- {
- // If Primary Attack
- if ((int)pTask->flTaskData == 1)
- {
- // -----------------------------------------------------------
- // If enemy isn't facing me and I haven't attacked in a while
- // annouce my attack before I start wailing away
- // -----------------------------------------------------------
- CBaseCombatCharacter *pBCC = GetEnemyCombatCharacterPointer();
-
- if (pBCC && pBCC->IsPlayer() && (!pBCC->FInViewCone ( this )) &&
- (gpGlobals->curtime - m_flLastAttackTime > 3.0) )
- {
- m_flLastAttackTime = gpGlobals->curtime;
-
- m_Sentences.Speak( "COMBINE_ANNOUNCE", SENTENCE_PRIORITY_HIGH );
-
- // Wait two seconds
- SetWait( 2.0 );
-
- if ( !IsCrouching() )
- {
- SetActivity(ACT_IDLE);
- }
- else
- {
- SetActivity(ACT_COWER); // This is really crouch idle
- }
- }
- // -------------------------------------------------------------
- // Otherwise move on
- // -------------------------------------------------------------
- else
- {
- TaskComplete();
- }
- }
- else
- {
- m_Sentences.Speak( "COMBINE_THROW_GRENADE", SENTENCE_PRIORITY_MEDIUM );
- SetActivity(ACT_IDLE);
-
- // Wait two seconds
- SetWait( 2.0 );
- }
- break;
- }
-
- case TASK_WALK_PATH:
- case TASK_RUN_PATH:
- // grunt no longer assumes he is covered if he moves
- Forget( bits_MEMORY_INCOVER );
- BaseClass::StartTask( pTask );
- break;
-
- case TASK_COMBINE_FACE_TOSS_DIR:
- break;
-
- case TASK_COMBINE_GET_PATH_TO_FORCED_GREN_LOS:
- {
- if ( !m_hForcedGrenadeTarget )
- {
- TaskFail(FAIL_NO_ENEMY);
- return;
- }
-
- float flMaxRange = 2000;
- float flMinRange = 0;
-
- Vector vecEnemy = m_hForcedGrenadeTarget->GetAbsOrigin();
- Vector vecEnemyEye = vecEnemy + m_hForcedGrenadeTarget->GetViewOffset();
-
- Vector posLos;
- bool found = false;
-
- if ( GetTacticalServices()->FindLateralLos( vecEnemyEye, &posLos ) )
- {
- float dist = ( posLos - vecEnemyEye ).Length();
- if ( dist < flMaxRange && dist > flMinRange )
- found = true;
- }
-
- if ( !found && GetTacticalServices()->FindLos( vecEnemy, vecEnemyEye, flMinRange, flMaxRange, 1.0, &posLos ) )
- {
- found = true;
- }
-
- if ( !found )
- {
- TaskFail( FAIL_NO_SHOOT );
- }
- else
- {
- // else drop into run task to offer an interrupt
- m_vInterruptSavePosition = posLos;
- }
- }
- break;
-
- case TASK_COMBINE_IGNORE_ATTACKS:
- // must be in a squad
- if (m_pSquad && m_pSquad->NumMembers() > 2)
- {
- // the enemy must be far enough away
- if (GetEnemy() && (GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter()).Length() > 512.0 )
- {
- m_flNextAttack = gpGlobals->curtime + pTask->flTaskData;
- }
- }
- TaskComplete( );
- break;
-
- case TASK_COMBINE_DEFER_SQUAD_GRENADES:
- {
- if ( m_pSquad )
- {
- // iterate my squad and stop everyone from throwing grenades for a little while.
- AISquadIter_t iter;
-
- CAI_BaseNPC *pSquadmate = m_pSquad ? m_pSquad->GetFirstMember( &iter ) : NULL;
- while ( pSquadmate )
- {
- CNPC_Combine *pCombine = dynamic_cast<CNPC_Combine*>(pSquadmate);
-
- if( pCombine )
- {
- pCombine->m_flNextGrenadeCheck = gpGlobals->curtime + 5;
- }
-
- pSquadmate = m_pSquad->GetNextMember( &iter );
- }
- }
-
- TaskComplete();
- break;
- }
-
- case TASK_FACE_IDEAL:
- case TASK_FACE_ENEMY:
- {
- if( pTask->iTask == TASK_FACE_ENEMY && HasCondition( COND_CAN_RANGE_ATTACK1 ) )
- {
- TaskComplete();
- return;
- }
-
- BaseClass::StartTask( pTask );
- bool bIsFlying = (GetMoveType() == MOVETYPE_FLY) || (GetMoveType() == MOVETYPE_FLYGRAVITY);
- if (bIsFlying)
- {
- SetIdealActivity( ACT_GLIDE );
- }
-
- }
- break;
-
- case TASK_FIND_COVER_FROM_ENEMY:
- {
- if (GetHintGroup() == NULL_STRING)
- {
- CBaseEntity *pEntity = GetEnemy();
-
- // FIXME: this should be generalized by the schedules that are selected, or in the definition of
- // what "cover" means (i.e., trace attack vulnerability vs. physical attack vulnerability
- if ( pEntity )
- {
- // NOTE: This is a good time to check to see if the player is hurt.
- // Have the combine notice this and call out
- if ( !HasMemory(bits_MEMORY_PLAYER_HURT) && pEntity->IsPlayer() && pEntity->GetHealth() <= 20 )
- {
- if ( m_pSquad )
- {
- m_pSquad->SquadRemember(bits_MEMORY_PLAYER_HURT);
- }
-
- m_Sentences.Speak( "COMBINE_PLAYERHIT", SENTENCE_PRIORITY_INVALID );
- JustMadeSound( SENTENCE_PRIORITY_HIGH );
- }
- if ( pEntity->MyNPCPointer() )
- {
- if ( !(pEntity->MyNPCPointer()->CapabilitiesGet( ) & bits_CAP_WEAPON_RANGE_ATTACK1) &&
- !(pEntity->MyNPCPointer()->CapabilitiesGet( ) & bits_CAP_INNATE_RANGE_ATTACK1) )
- {
- TaskComplete();
- return;
- }
- }
- }
- }
- BaseClass::StartTask( pTask );
- }
- break;
- case TASK_RANGE_ATTACK1:
- {
- m_nShots = GetActiveWeapon()->GetRandomBurst();
- m_flShotDelay = GetActiveWeapon()->GetFireRate();
-
- m_flNextAttack = gpGlobals->curtime + m_flShotDelay - 0.1;
- ResetIdealActivity( ACT_RANGE_ATTACK1 );
- m_flLastAttackTime = gpGlobals->curtime;
- }
- break;
-
- case TASK_COMBINE_DIE_INSTANTLY:
- {
- CTakeDamageInfo info;
-
- info.SetAttacker( this );
- info.SetInflictor( this );
- info.SetDamage( m_iHealth );
- info.SetDamageType( pTask->flTaskData );
- info.SetDamageForce( Vector( 0.1, 0.1, 0.1 ) );
-
- TakeDamage( info );
-
- TaskComplete();
- }
- break;
-
- default:
- BaseClass:: StartTask( pTask );
- break;
- }
-}
-
-//=========================================================
-// RunTask
-//=========================================================
-void CNPC_Combine::RunTask( const Task_t *pTask )
-{
- /*
- {
- CBaseEntity *pEnemy = GetEnemy();
- if (pEnemy)
- {
- NDebugOverlay::Line(Center(), pEnemy->Center(), 0,255,255, false, 0.1);
- }
-
- }
- */
-
- /*
- if (m_iMySquadSlot != SQUAD_SLOT_NONE)
- {
- char text[64];
- Q_snprintf( text, strlen( text ), "%d", m_iMySquadSlot );
-
- NDebugOverlay::Text( Center() + Vector( 0, 0, 72 ), text, false, 0.1 );
- }
- */
-
- switch ( pTask->iTask )
- {
- case TASK_COMBINE_CHASE_ENEMY_CONTINUOUSLY:
- RunTaskChaseEnemyContinuously( pTask );
- break;
-
- case TASK_COMBINE_SIGNAL_BEST_SOUND:
- AutoMovement( );
- if ( IsActivityFinished() )
- {
- TaskComplete();
- }
- break;
-
- case TASK_ANNOUNCE_ATTACK:
- {
- // Stop waiting if enemy facing me or lost enemy
- CBaseCombatCharacter* pBCC = GetEnemyCombatCharacterPointer();
- if (!pBCC || pBCC->FInViewCone( this ))
- {
- TaskComplete();
- }
-
- if ( IsWaitFinished() )
- {
- TaskComplete();
- }
- }
- break;
-
- case TASK_COMBINE_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET:
- GetMotor()->SetIdealYawToTargetAndUpdate( m_vecAltFireTarget, AI_KEEP_YAW_SPEED );
-
- if ( IsActivityFinished() )
- {
- TaskComplete();
- }
- break;
-
- case TASK_COMBINE_FACE_TOSS_DIR:
- {
- // project a point along the toss vector and turn to face that point.
- GetMotor()->SetIdealYawToTargetAndUpdate( GetLocalOrigin() + m_vecTossVelocity * 64, AI_KEEP_YAW_SPEED );
-
- if ( FacingIdeal() )
- {
- TaskComplete( true );
- }
- break;
- }
-
- case TASK_COMBINE_GET_PATH_TO_FORCED_GREN_LOS:
- {
- if ( !m_hForcedGrenadeTarget )
- {
- TaskFail(FAIL_NO_ENEMY);
- return;
- }
-
- if ( GetTaskInterrupt() > 0 )
- {
- ClearTaskInterrupt();
-
- Vector vecEnemy = m_hForcedGrenadeTarget->GetAbsOrigin();
- AI_NavGoal_t goal( m_vInterruptSavePosition, ACT_RUN, AIN_HULL_TOLERANCE );
-
- GetNavigator()->SetGoal( goal, AIN_CLEAR_TARGET );
- GetNavigator()->SetArrivalDirection( vecEnemy - goal.dest );
- }
- else
- {
- TaskInterrupt();
- }
- }
- break;
-
- case TASK_RANGE_ATTACK1:
- {
- AutoMovement( );
-
- Vector vecEnemyLKP = GetEnemyLKP();
- if (!FInAimCone( vecEnemyLKP ))
- {
- GetMotor()->SetIdealYawToTargetAndUpdate( vecEnemyLKP, AI_KEEP_YAW_SPEED );
- }
- else
- {
- GetMotor()->SetIdealYawAndUpdate( GetMotor()->GetIdealYaw(), AI_KEEP_YAW_SPEED );
- }
-
- if ( gpGlobals->curtime >= m_flNextAttack )
- {
- if ( IsActivityFinished() )
- {
- if (--m_nShots > 0)
- {
- // DevMsg("ACT_RANGE_ATTACK1\n");
- ResetIdealActivity( ACT_RANGE_ATTACK1 );
- m_flLastAttackTime = gpGlobals->curtime;
- m_flNextAttack = gpGlobals->curtime + m_flShotDelay - 0.1;
- }
- else
- {
- // DevMsg("TASK_RANGE_ATTACK1 complete\n");
- TaskComplete();
- }
- }
- }
- else
- {
- // DevMsg("Wait\n");
- }
- }
- break;
-
- default:
- {
- BaseClass::RunTask( pTask );
- break;
- }
- }
-}
-
-//------------------------------------------------------------------------------
-// Purpose : Override to always shoot at eyes (for ducking behind things)
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-Vector CNPC_Combine::BodyTarget( const Vector &posSrc, bool bNoisy )
-{
- Vector result = BaseClass::BodyTarget( posSrc, bNoisy );
-
- // @TODO (toml 02-02-04): this seems wrong. Isn't this already be accounted for
- // with the eye position used in the base BodyTarget()
- if ( GetFlags() & FL_DUCKING )
- result -= Vector(0,0,24);
-
- return result;
-}
-
-//------------------------------------------------------------------------------
-// Purpose:
-//------------------------------------------------------------------------------
-bool CNPC_Combine::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
-{
- if( m_spawnflags & SF_COMBINE_NO_LOOK )
- {
- // When no look is set, if enemy has eluded the squad,
- // he's always invisble to me
- if (GetEnemies()->HasEludedMe(pEntity))
- {
- return false;
- }
- }
- return BaseClass::FVisible(pEntity, traceMask, ppBlocker);
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Combine::Event_Killed( const CTakeDamageInfo &info )
-{
- // if I was killed before I could finish throwing my grenade, drop
- // a grenade item that the player can retrieve.
- if( GetActivity() == ACT_RANGE_ATTACK2 )
- {
- if( m_iLastAnimEventHandled != COMBINE_AE_GREN_TOSS )
- {
- // Drop the grenade as an item.
- Vector vecStart;
- GetAttachment( "lefthand", vecStart );
-
- CBaseEntity *pItem = DropItem( "weapon_frag", vecStart, RandomAngle(0,360) );
-
- if ( pItem )
- {
- IPhysicsObject *pObj = pItem->VPhysicsGetObject();
-
- if ( pObj )
- {
- Vector vel;
- vel.x = random->RandomFloat( -100.0f, 100.0f );
- vel.y = random->RandomFloat( -100.0f, 100.0f );
- vel.z = random->RandomFloat( 800.0f, 1200.0f );
- AngularImpulse angImp = RandomAngularImpulse( -300.0f, 300.0f );
-
- vel[2] = 0.0f;
- pObj->AddVelocity( &vel, &angImp );
- }
-
- // In the Citadel we need to dissolve this
- if ( PlayerHasMegaPhysCannon() )
- {
- CBaseCombatWeapon *pWeapon = static_cast<CBaseCombatWeapon *>(pItem);
-
- pWeapon->Dissolve( NULL, gpGlobals->curtime, false, ENTITY_DISSOLVE_NORMAL );
- }
- }
- }
- }
-
- BaseClass::Event_Killed( info );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Override. Don't update if I'm not looking
-// Input :
-// Output : Returns true is new enemy, false is known enemy
-//-----------------------------------------------------------------------------
-bool CNPC_Combine::UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer )
-{
- if( m_spawnflags & SF_COMBINE_NO_LOOK )
- {
- return false;
- }
-
- return BaseClass::UpdateEnemyMemory( pEnemy, position, pInformer );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Allows for modification of the interrupt mask for the current schedule.
-// In the most cases the base implementation should be called first.
-//-----------------------------------------------------------------------------
-void CNPC_Combine::BuildScheduleTestBits( void )
-{
- BaseClass::BuildScheduleTestBits();
-
- if (gpGlobals->curtime < m_flNextAttack)
- {
- ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 );
- ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 );
- }
-
- SetCustomInterruptCondition( COND_COMBINE_HIT_BY_BUGBAIT );
-
- if ( !IsCurSchedule( SCHED_COMBINE_BURNING_STAND ) )
- {
- SetCustomInterruptCondition( COND_COMBINE_ON_FIRE );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Translate base class activities into combot activites
-//-----------------------------------------------------------------------------
-Activity CNPC_Combine::NPC_TranslateActivity( Activity eNewActivity )
-{
- //Slaming this back to ACT_COMBINE_BUGBAIT since we don't want ANYTHING to change our activity while we burn.
- if ( HasCondition( COND_COMBINE_ON_FIRE ) )
- return BaseClass::NPC_TranslateActivity( ACT_COMBINE_BUGBAIT );
-
- if (eNewActivity == ACT_RANGE_ATTACK2)
- {
- // grunt is going to a secondary long range attack. This may be a thrown
- // grenade or fired grenade, we must determine which and pick proper sequence
- if (Weapon_OwnsThisType( "weapon_grenadelauncher" ) )
- {
- return ( Activity )ACT_COMBINE_LAUNCH_GRENADE;
- }
- else
- {
- return ( Activity )ACT_COMBINE_THROW_GRENADE;
- }
- }
- else if (eNewActivity == ACT_IDLE)
- {
- if ( !IsCrouching() && ( m_NPCState == NPC_STATE_COMBAT || m_NPCState == NPC_STATE_ALERT ) )
- {
- eNewActivity = ACT_IDLE_ANGRY;
- }
- }
-
- if ( m_AssaultBehavior.IsRunning() )
- {
- switch ( eNewActivity )
- {
- case ACT_IDLE:
- eNewActivity = ACT_IDLE_ANGRY;
- break;
-
- case ACT_WALK:
- eNewActivity = ACT_WALK_AIM;
- break;
-
- case ACT_RUN:
- eNewActivity = ACT_RUN_AIM;
- break;
- }
- }
-
- return BaseClass::NPC_TranslateActivity( eNewActivity );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Overidden for human grunts because they hear the DANGER sound
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-int CNPC_Combine::GetSoundInterests( void )
-{
- return SOUND_WORLD | SOUND_COMBAT | SOUND_PLAYER | SOUND_DANGER | SOUND_PHYSICS_DANGER | SOUND_BULLET_IMPACT | SOUND_MOVE_AWAY;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Return true if this NPC can hear the specified sound
-//-----------------------------------------------------------------------------
-bool CNPC_Combine::QueryHearSound( CSound *pSound )
-{
- if ( pSound->SoundContext() & SOUND_CONTEXT_COMBINE_ONLY )
- return true;
-
- if ( pSound->SoundContext() & SOUND_CONTEXT_EXCLUDE_COMBINE )
- return false;
-
- return BaseClass::QueryHearSound( pSound );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Announce an assault if the enemy can see me and we are pretty
-// close to him/her
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-void CNPC_Combine::AnnounceAssault(void)
-{
- if (random->RandomInt(0,5) > 1)
- return;
-
- // If enemy can see me make assualt sound
- CBaseCombatCharacter* pBCC = GetEnemyCombatCharacterPointer();
-
- if (!pBCC)
- return;
-
- if (!FOkToMakeSound())
- return;
-
- // Make sure we are pretty close
- if ( WorldSpaceCenter().DistToSqr( pBCC->WorldSpaceCenter() ) > (2000 * 2000))
- return;
-
- // Make sure we are in view cone of player
- if (!pBCC->FInViewCone ( this ))
- return;
-
- // Make sure player can see me
- if ( FVisible( pBCC ) )
- {
- m_Sentences.Speak( "COMBINE_ASSAULT" );
- }
-}
-
-
-void CNPC_Combine::AnnounceEnemyType( CBaseEntity *pEnemy )
-{
- const char *pSentenceName = "COMBINE_MONST";
- switch ( pEnemy->Classify() )
- {
- case CLASS_PLAYER:
- pSentenceName = "COMBINE_ALERT";
- break;
-
- case CLASS_PLAYER_ALLY:
- case CLASS_CITIZEN_REBEL:
- case CLASS_CITIZEN_PASSIVE:
- case CLASS_VORTIGAUNT:
- pSentenceName = "COMBINE_MONST_CITIZENS";
- break;
-
- case CLASS_PLAYER_ALLY_VITAL:
- pSentenceName = "COMBINE_MONST_CHARACTER";
- break;
-
- case CLASS_ANTLION:
- pSentenceName = "COMBINE_MONST_BUGS";
- break;
-
- case CLASS_ZOMBIE:
- pSentenceName = "COMBINE_MONST_ZOMBIES";
- break;
-
- case CLASS_HEADCRAB:
- case CLASS_BARNACLE:
- pSentenceName = "COMBINE_MONST_PARASITES";
- break;
- }
-
- m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_HIGH );
-}
-
-void CNPC_Combine::AnnounceEnemyKill( CBaseEntity *pEnemy )
-{
- if (!pEnemy )
- return;
-
- const char *pSentenceName = "COMBINE_KILL_MONST";
- switch ( pEnemy->Classify() )
- {
- case CLASS_PLAYER:
- pSentenceName = "COMBINE_PLAYER_DEAD";
- break;
-
- // no sentences for these guys yet
- case CLASS_PLAYER_ALLY:
- case CLASS_CITIZEN_REBEL:
- case CLASS_CITIZEN_PASSIVE:
- case CLASS_VORTIGAUNT:
- break;
-
- case CLASS_PLAYER_ALLY_VITAL:
- break;
-
- case CLASS_ANTLION:
- break;
-
- case CLASS_ZOMBIE:
- break;
-
- case CLASS_HEADCRAB:
- case CLASS_BARNACLE:
- break;
- }
-
- m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_HIGH );
-}
-
-//-----------------------------------------------------------------------------
-// Select the combat schedule
-//-----------------------------------------------------------------------------
-int CNPC_Combine::SelectCombatSchedule()
-{
- // -----------
- // dead enemy
- // -----------
- if ( HasCondition( COND_ENEMY_DEAD ) )
- {
- // call base class, all code to handle dead enemies is centralized there.
- return SCHED_NONE;
- }
-
- // -----------
- // new enemy
- // -----------
- if ( HasCondition( COND_NEW_ENEMY ) )
- {
- CBaseEntity *pEnemy = GetEnemy();
- bool bFirstContact = false;
- float flTimeSinceFirstSeen = gpGlobals->curtime - GetEnemies()->FirstTimeSeen( pEnemy );
-
- if( flTimeSinceFirstSeen < 3.0f )
- bFirstContact = true;
-
- if ( m_pSquad && pEnemy )
- {
- if ( HasCondition( COND_SEE_ENEMY ) )
- {
- AnnounceEnemyType( pEnemy );
- }
-
- if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) && OccupyStrategySlot( SQUAD_SLOT_ATTACK1 ) )
- {
- // Start suppressing if someone isn't firing already (SLOT_ATTACK1). This means
- // I'm the guy who spotted the enemy, I should react immediately.
- return SCHED_COMBINE_SUPPRESS;
- }
-
- if ( m_pSquad->IsLeader( this ) || ( m_pSquad->GetLeader() && m_pSquad->GetLeader()->GetEnemy() != pEnemy ) )
- {
- // I'm the leader, but I didn't get the job suppressing the enemy. We know this because
- // This code only runs if the code above didn't assign me SCHED_COMBINE_SUPPRESS.
- if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) && OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
- {
- return SCHED_RANGE_ATTACK1;
- }
-
- if( HasCondition(COND_WEAPON_HAS_LOS) && IsStrategySlotRangeOccupied( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
- {
- // If everyone else is attacking and I have line of fire, wait for a chance to cover someone.
- if( OccupyStrategySlot( SQUAD_SLOT_OVERWATCH ) )
- {
- return SCHED_COMBINE_ENTER_OVERWATCH;
- }
- }
- }
- else
- {
- if ( m_pSquad->GetLeader() && FOkToMakeSound( SENTENCE_PRIORITY_MEDIUM ) )
- {
- JustMadeSound( SENTENCE_PRIORITY_MEDIUM ); // squelch anything that isn't high priority so the leader can speak
- }
-
- // First contact, and I'm solo, or not the squad leader.
- if( HasCondition( COND_SEE_ENEMY ) && CanGrenadeEnemy() )
- {
- if( OccupyStrategySlot( SQUAD_SLOT_GRENADE1 ) )
- {
- return SCHED_RANGE_ATTACK2;
- }
- }
-
- if( !bFirstContact && OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
- {
- if( random->RandomInt(0, 100) < 60 )
- {
- return SCHED_ESTABLISH_LINE_OF_FIRE;
- }
- else
- {
- return SCHED_COMBINE_PRESS_ATTACK;
- }
- }
-
- return SCHED_TAKE_COVER_FROM_ENEMY;
- }
- }
- }
-
- // ---------------------
- // no ammo
- // ---------------------
- if ( ( HasCondition ( COND_NO_PRIMARY_AMMO ) || HasCondition ( COND_LOW_PRIMARY_AMMO ) ) && !HasCondition( COND_CAN_MELEE_ATTACK1) )
- {
- return SCHED_HIDE_AND_RELOAD;
- }
-
- // ----------------------
- // LIGHT DAMAGE
- // ----------------------
- if ( HasCondition( COND_LIGHT_DAMAGE ) )
- {
- if ( GetEnemy() != NULL )
- {
- // only try to take cover if we actually have an enemy!
-
- // FIXME: need to take cover for enemy dealing the damage
-
- // A standing guy will either crouch or run.
- // A crouching guy tries to stay stuck in.
- if( !IsCrouching() )
- {
- if( GetEnemy() && random->RandomFloat( 0, 100 ) < 50 && CouldShootIfCrouching( GetEnemy() ) )
- {
- Crouch();
- }
- else
- {
- //!!!KELLY - this grunt was hit and is going to run to cover.
- // m_Sentences.Speak( "COMBINE_COVER" );
- return SCHED_TAKE_COVER_FROM_ENEMY;
- }
- }
- }
- else
- {
- // How am I wounded in combat with no enemy?
- Assert( GetEnemy() != NULL );
- }
- }
-
- // If I'm scared of this enemy run away
- if ( IRelationType( GetEnemy() ) == D_FR )
- {
- if (HasCondition( COND_SEE_ENEMY ) ||
- HasCondition( COND_SEE_FEAR ) ||
- HasCondition( COND_LIGHT_DAMAGE ) ||
- HasCondition( COND_HEAVY_DAMAGE ))
- {
- FearSound();
- //ClearCommandGoal();
- return SCHED_RUN_FROM_ENEMY;
- }
-
- // If I've seen the enemy recently, cower. Ignore the time for unforgettable enemies.
- AI_EnemyInfo_t *pMemory = GetEnemies()->Find( GetEnemy() );
- if ( (pMemory && pMemory->bUnforgettable) || (GetEnemyLastTimeSeen() > (gpGlobals->curtime - 5.0)) )
- {
- // If we're facing him, just look ready. Otherwise, face him.
- if ( FInAimCone( GetEnemy()->EyePosition() ) )
- return SCHED_COMBAT_STAND;
-
- return SCHED_FEAR_FACE;
- }
- }
-
- int attackSchedule = SelectScheduleAttack();
- if ( attackSchedule != SCHED_NONE )
- return attackSchedule;
-
- if (HasCondition(COND_ENEMY_OCCLUDED))
- {
- // stand up, just in case
- Stand();
- DesireStand();
-
- if( GetEnemy() && !(GetEnemy()->GetFlags() & FL_NOTARGET) && OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
- {
- // Charge in and break the enemy's cover!
- return SCHED_ESTABLISH_LINE_OF_FIRE;
- }
-
- // If I'm a long, long way away, establish a LOF anyway. Once I get there I'll
- // start respecting the squad slots again.
- float flDistSq = GetEnemy()->WorldSpaceCenter().DistToSqr( WorldSpaceCenter() );
- if ( flDistSq > Square(3000) )
- return SCHED_ESTABLISH_LINE_OF_FIRE;
-
- // Otherwise tuck in.
- Remember( bits_MEMORY_INCOVER );
- return SCHED_COMBINE_WAIT_IN_COVER;
- }
-
- // --------------------------------------------------------------
- // Enemy not occluded but isn't open to attack
- // --------------------------------------------------------------
- if ( HasCondition( COND_SEE_ENEMY ) && !HasCondition( COND_CAN_RANGE_ATTACK1 ) )
- {
- if ( (HasCondition( COND_TOO_FAR_TO_ATTACK ) || IsUsingTacticalVariant(TACTICAL_VARIANT_PRESSURE_ENEMY) ) && OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ))
- {
- return SCHED_COMBINE_PRESS_ATTACK;
- }
-
- AnnounceAssault();
- return SCHED_COMBINE_ASSAULT;
- }
-
- return SCHED_NONE;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-int CNPC_Combine::SelectSchedule( void )
-{
- if ( IsWaitingToRappel() && BehaviorSelectSchedule() )
- {
- return BaseClass::SelectSchedule();
- }
-
- if ( HasCondition(COND_COMBINE_ON_FIRE) )
- return SCHED_COMBINE_BURNING_STAND;
-
- int nSched = SelectFlinchSchedule();
- if ( nSched != SCHED_NONE )
- return nSched;
-
- if ( m_hForcedGrenadeTarget )
- {
- if ( m_flNextGrenadeCheck < gpGlobals->curtime )
- {
- Vector vecTarget = m_hForcedGrenadeTarget->WorldSpaceCenter();
-
- if ( IsElite() )
- {
- if ( FVisible( m_hForcedGrenadeTarget ) )
- {
- m_vecAltFireTarget = vecTarget;
- m_hForcedGrenadeTarget = NULL;
- return SCHED_COMBINE_AR2_ALTFIRE;
- }
- }
- else
- {
- // If we can, throw a grenade at the target.
- // Ignore grenade count / distance / etc
- if ( CheckCanThrowGrenade( vecTarget ) )
- {
- m_hForcedGrenadeTarget = NULL;
- return SCHED_COMBINE_FORCED_GRENADE_THROW;
- }
- }
- }
-
- // Can't throw at the target, so lets try moving to somewhere where I can see it
- if ( !FVisible( m_hForcedGrenadeTarget ) )
- {
- return SCHED_COMBINE_MOVE_TO_FORCED_GREN_LOS;
- }
- }
-
- if ( m_NPCState != NPC_STATE_SCRIPT)
- {
- // If we're hit by bugbait, thrash around
- if ( HasCondition( COND_COMBINE_HIT_BY_BUGBAIT ) )
- {
- // Don't do this if we're mounting a func_tank
- if ( m_FuncTankBehavior.IsMounted() == true )
- {
- m_FuncTankBehavior.Dismount();
- }
-
- ClearCondition( COND_COMBINE_HIT_BY_BUGBAIT );
- return SCHED_COMBINE_BUGBAIT_DISTRACTION;
- }
-
- // We've been told to move away from a target to make room for a grenade to be thrown at it
- if ( HasCondition( COND_HEAR_MOVE_AWAY ) )
- {
- return SCHED_MOVE_AWAY;
- }
-
- // These things are done in any state but dead and prone
- if (m_NPCState != NPC_STATE_DEAD && m_NPCState != NPC_STATE_PRONE )
- {
- // Cower when physics objects are thrown at me
- if ( HasCondition( COND_HEAR_PHYSICS_DANGER ) )
- {
- return SCHED_FLINCH_PHYSICS;
- }
-
- // grunts place HIGH priority on running away from danger sounds.
- if ( HasCondition(COND_HEAR_DANGER) )
- {
- CSound *pSound;
- pSound = GetBestSound();
-
- Assert( pSound != NULL );
- if ( pSound)
- {
- if (pSound->m_iType & SOUND_DANGER)
- {
- // I hear something dangerous, probably need to take cover.
- // dangerous sound nearby!, call it out
- const char *pSentenceName = "COMBINE_DANGER";
-
- CBaseEntity *pSoundOwner = pSound->m_hOwner;
- if ( pSoundOwner )
- {
- CBaseGrenade *pGrenade = dynamic_cast<CBaseGrenade *>(pSoundOwner);
- if ( pGrenade && pGrenade->GetThrower() )
- {
- if ( IRelationType( pGrenade->GetThrower() ) != D_LI )
- {
- // special case call out for enemy grenades
- pSentenceName = "COMBINE_GREN";
- }
- }
- }
-
- m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_NORMAL, SENTENCE_CRITERIA_NORMAL );
-
- // If the sound is approaching danger, I have no enemy, and I don't see it, turn to face.
- if( !GetEnemy() && pSound->IsSoundType(SOUND_CONTEXT_DANGER_APPROACH) && pSound->m_hOwner && !FInViewCone(pSound->GetSoundReactOrigin()) )
- {
- GetMotor()->SetIdealYawToTarget( pSound->GetSoundReactOrigin() );
- return SCHED_COMBINE_FACE_IDEAL_YAW;
- }
-
- return SCHED_TAKE_COVER_FROM_BEST_SOUND;
- }
-
- // JAY: This was disabled in HL1. Test?
- if (!HasCondition( COND_SEE_ENEMY ) && ( pSound->m_iType & (SOUND_PLAYER | SOUND_COMBAT) ))
- {
- GetMotor()->SetIdealYawToTarget( pSound->GetSoundReactOrigin() );
- }
- }
- }
- }
-
- if( BehaviorSelectSchedule() )
- {
- return BaseClass::SelectSchedule();
- }
- }
-
- switch ( m_NPCState )
- {
- case NPC_STATE_IDLE:
- {
- if ( m_bShouldPatrol )
- return SCHED_COMBINE_PATROL;
- }
- // NOTE: Fall through!
-
- case NPC_STATE_ALERT:
- {
- if( HasCondition(COND_LIGHT_DAMAGE) || HasCondition(COND_HEAVY_DAMAGE) )
- {
- AI_EnemyInfo_t *pDanger = GetEnemies()->GetDangerMemory();
- if( pDanger && FInViewCone(pDanger->vLastKnownLocation) && !BaseClass::FVisible(pDanger->vLastKnownLocation) )
- {
- // I've been hurt, I'm facing the danger, but I don't see it, so move from this position.
- return SCHED_TAKE_COVER_FROM_ORIGIN;
- }
- }
-
- if( HasCondition( COND_HEAR_COMBAT ) )
- {
- CSound *pSound = GetBestSound();
-
- if( pSound && pSound->IsSoundType( SOUND_COMBAT ) )
- {
- if( m_pSquad && m_pSquad->GetSquadMemberNearestTo( pSound->GetSoundReactOrigin() ) == this && OccupyStrategySlot( SQUAD_SLOT_INVESTIGATE_SOUND ) )
- {
- return SCHED_INVESTIGATE_SOUND;
- }
- }
- }
-
- // Don't patrol if I'm in the middle of an assault, because I'll never return to the assault.
- if ( !m_AssaultBehavior.HasAssaultCue() )
- {
- if( m_bShouldPatrol || HasCondition( COND_COMBINE_SHOULD_PATROL ) )
- return SCHED_COMBINE_PATROL;
- }
- }
- break;
-
- case NPC_STATE_COMBAT:
- {
- int nSched = SelectCombatSchedule();
- if ( nSched != SCHED_NONE )
- return nSched;
- }
- break;
- }
-
- // no special cases here, call the base class
- return BaseClass::SelectSchedule();
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Combine::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
-{
- if( failedSchedule == SCHED_COMBINE_TAKE_COVER1 )
- {
- if( IsInSquad() && IsStrategySlotRangeOccupied(SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2) && HasCondition(COND_SEE_ENEMY) )
- {
- // This eases the effects of an unfortunate bug that usually plagues shotgunners. Since their rate of fire is low,
- // they spend relatively long periods of time without an attack squad slot. If you corner a shotgunner, usually
- // the other memebers of the squad will hog all of the attack slots and pick schedules to move to establish line of
- // fire. During this time, the shotgunner is prevented from attacking. If he also cannot find cover (the fallback case)
- // he will stand around like an idiot, right in front of you. Instead of this, we have him run up to you for a melee attack.
- return SCHED_COMBINE_MOVE_TO_MELEE;
- }
- }
-
- return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
-}
-
-//-----------------------------------------------------------------------------
-// Should we charge the player?
-//-----------------------------------------------------------------------------
-bool CNPC_Combine::ShouldChargePlayer()
-{
- return GetEnemy() && GetEnemy()->IsPlayer() && PlayerHasMegaPhysCannon() && !IsLimitingHintGroups();
-}
-
-
-//-----------------------------------------------------------------------------
-// Select attack schedules
-//-----------------------------------------------------------------------------
-#define COMBINE_MEGA_PHYSCANNON_ATTACK_DISTANCE 192
-#define COMBINE_MEGA_PHYSCANNON_ATTACK_DISTANCE_SQ (COMBINE_MEGA_PHYSCANNON_ATTACK_DISTANCE*COMBINE_MEGA_PHYSCANNON_ATTACK_DISTANCE)
-
-int CNPC_Combine::SelectScheduleAttack()
-{
- // Drop a grenade?
- if ( HasCondition( COND_COMBINE_DROP_GRENADE ) )
- return SCHED_COMBINE_DROP_GRENADE;
-
- // Kick attack?
- if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
- {
- return SCHED_MELEE_ATTACK1;
- }
-
- // If I'm fighting a combine turret (it's been hacked to attack me), I can't really
- // hurt it with bullets, so become grenade happy.
- if ( GetEnemy() && GetEnemy()->Classify() == CLASS_COMBINE && FClassnameIs(GetEnemy(), "npc_turret_floor") )
- {
- // Don't do this until I've been fighting the turret for a few seconds
- float flTimeAtFirstHand = GetEnemies()->TimeAtFirstHand(GetEnemy());
- if ( flTimeAtFirstHand != AI_INVALID_TIME )
- {
- float flTimeEnemySeen = gpGlobals->curtime - flTimeAtFirstHand;
- if ( flTimeEnemySeen > 4.0 )
- {
- if ( CanGrenadeEnemy() && OccupyStrategySlot( SQUAD_SLOT_GRENADE1 ) )
- return SCHED_RANGE_ATTACK2;
- }
- }
-
- // If we're not in the viewcone of the turret, run up and hit it. Do this a bit later to
- // give other squadmembers a chance to throw a grenade before I run in.
- if ( !GetEnemy()->MyNPCPointer()->FInViewCone( this ) && OccupyStrategySlot( SQUAD_SLOT_GRENADE1 ) )
- return SCHED_COMBINE_CHARGE_TURRET;
- }
-
- // When fighting against the player who's wielding a mega-physcannon,
- // always close the distance if possible
- // But don't do it if you're in a nav-limited hint group
- if ( ShouldChargePlayer() )
- {
- float flDistSq = GetEnemy()->WorldSpaceCenter().DistToSqr( WorldSpaceCenter() );
- if ( flDistSq <= COMBINE_MEGA_PHYSCANNON_ATTACK_DISTANCE_SQ )
- {
- if( HasCondition(COND_SEE_ENEMY) )
- {
- if ( OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
- return SCHED_RANGE_ATTACK1;
- }
- else
- {
- if ( OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
- return SCHED_COMBINE_PRESS_ATTACK;
- }
- }
-
- if ( HasCondition(COND_SEE_ENEMY) && !IsUnreachable( GetEnemy() ) )
- {
- return SCHED_COMBINE_CHARGE_PLAYER;
- }
- }
-
- // Can I shoot?
- if ( HasCondition(COND_CAN_RANGE_ATTACK1) )
- {
-
- // JAY: HL1 behavior missing?
-#if 0
- if ( m_pSquad )
- {
- // if the enemy has eluded the squad and a squad member has just located the enemy
- // and the enemy does not see the squad member, issue a call to the squad to waste a
- // little time and give the player a chance to turn.
- if ( MySquadLeader()->m_fEnemyEluded && !HasConditions ( bits_COND_ENEMY_FACING_ME ) )
- {
- MySquadLeader()->m_fEnemyEluded = FALSE;
- return SCHED_GRUNT_FOUND_ENEMY;
- }
- }
-#endif
-
- // Engage if allowed
- if ( OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
- {
- return SCHED_RANGE_ATTACK1;
- }
-
- // Throw a grenade if not allowed to engage with weapon.
- if ( CanGrenadeEnemy() )
- {
- if ( OccupyStrategySlot( SQUAD_SLOT_GRENADE1 ) )
- {
- return SCHED_RANGE_ATTACK2;
- }
- }
-
- DesireCrouch();
- return SCHED_TAKE_COVER_FROM_ENEMY;
- }
-
- if ( GetEnemy() && !HasCondition(COND_SEE_ENEMY) )
- {
- // We don't see our enemy. If it hasn't been long since I last saw him,
- // and he's pretty close to the last place I saw him, throw a grenade in
- // to flush him out. A wee bit of cheating here...
-
- float flTime;
- float flDist;
-
- flTime = gpGlobals->curtime - GetEnemies()->LastTimeSeen( GetEnemy() );
- flDist = ( GetEnemy()->GetAbsOrigin() - GetEnemies()->LastSeenPosition( GetEnemy() ) ).Length();
-
- //Msg("Time: %f Dist: %f\n", flTime, flDist );
- if ( flTime <= COMBINE_GRENADE_FLUSH_TIME && flDist <= COMBINE_GRENADE_FLUSH_DIST && CanGrenadeEnemy( false ) && OccupyStrategySlot( SQUAD_SLOT_GRENADE1 ) )
- {
- return SCHED_RANGE_ATTACK2;
- }
- }
-
- if (HasCondition(COND_WEAPON_SIGHT_OCCLUDED))
- {
- // If they are hiding behind something that we can destroy, start shooting at it.
- CBaseEntity *pBlocker = GetEnemyOccluder();
- if ( pBlocker && pBlocker->GetHealth() > 0 && OccupyStrategySlot( SQUAD_SLOT_ATTACK_OCCLUDER ) )
- {
- return SCHED_SHOOT_ENEMY_COVER;
- }
- }
-
- return SCHED_NONE;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-int CNPC_Combine::TranslateSchedule( int scheduleType )
-{
- switch( scheduleType )
- {
- case SCHED_TAKE_COVER_FROM_ENEMY:
- {
- if ( m_pSquad )
- {
- // Have to explicitly check innate range attack condition as may have weapon with range attack 2
- if ( g_pGameRules->IsSkillLevel( SKILL_HARD ) &&
- HasCondition(COND_CAN_RANGE_ATTACK2) &&
- OccupyStrategySlot( SQUAD_SLOT_GRENADE1 ) )
- {
- m_Sentences.Speak( "COMBINE_THROW_GRENADE" );
- return SCHED_COMBINE_TOSS_GRENADE_COVER1;
- }
- else
- {
- if ( ShouldChargePlayer() && !IsUnreachable( GetEnemy() ) )
- return SCHED_COMBINE_CHARGE_PLAYER;
-
- return SCHED_COMBINE_TAKE_COVER1;
- }
- }
- else
- {
- // Have to explicitly check innate range attack condition as may have weapon with range attack 2
- if ( random->RandomInt(0,1) && HasCondition(COND_CAN_RANGE_ATTACK2) )
- {
- return SCHED_COMBINE_GRENADE_COVER1;
- }
- else
- {
- if ( ShouldChargePlayer() && !IsUnreachable( GetEnemy() ) )
- return SCHED_COMBINE_CHARGE_PLAYER;
-
- return SCHED_COMBINE_TAKE_COVER1;
- }
- }
- }
- case SCHED_TAKE_COVER_FROM_BEST_SOUND:
- {
- return SCHED_COMBINE_TAKE_COVER_FROM_BEST_SOUND;
- }
- break;
- case SCHED_COMBINE_TAKECOVER_FAILED:
- {
- if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) && OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
- {
- return TranslateSchedule( SCHED_RANGE_ATTACK1 );
- }
-
- // Run somewhere randomly
- return TranslateSchedule( SCHED_FAIL );
- break;
- }
- break;
- case SCHED_FAIL_ESTABLISH_LINE_OF_FIRE:
- {
- if( !IsCrouching() )
- {
- if( GetEnemy() && CouldShootIfCrouching( GetEnemy() ) )
- {
- Crouch();
- return SCHED_COMBAT_FACE;
- }
- }
-
- if( HasCondition( COND_SEE_ENEMY ) )
- {
- return TranslateSchedule( SCHED_TAKE_COVER_FROM_ENEMY );
- }
- else if ( !m_AssaultBehavior.HasAssaultCue() )
- {
- // Don't patrol if I'm in the middle of an assault, because
- // I'll never return to the assault.
- if ( GetEnemy() )
- {
- RememberUnreachable( GetEnemy() );
- }
-
- return TranslateSchedule( SCHED_COMBINE_PATROL );
- }
- }
- break;
- case SCHED_COMBINE_ASSAULT:
- {
- CBaseEntity *pEntity = GetEnemy();
-
- // FIXME: this should be generalized by the schedules that are selected, or in the definition of
- // what "cover" means (i.e., trace attack vulnerability vs. physical attack vulnerability
- if (pEntity && pEntity->MyNPCPointer())
- {
- if ( !(pEntity->MyNPCPointer()->CapabilitiesGet( ) & bits_CAP_WEAPON_RANGE_ATTACK1))
- {
- return TranslateSchedule( SCHED_ESTABLISH_LINE_OF_FIRE );
- }
- }
- // don't charge forward if there's a hint group
- if (GetHintGroup() != NULL_STRING)
- {
- return TranslateSchedule( SCHED_ESTABLISH_LINE_OF_FIRE );
- }
- return SCHED_COMBINE_ASSAULT;
- }
- case SCHED_ESTABLISH_LINE_OF_FIRE:
- {
- // always assume standing
- // Stand();
-
- if( CanAltFireEnemy(true) && OccupyStrategySlot(SQUAD_SLOT_SPECIAL_ATTACK) )
- {
- // If an elite in the squad could fire a combine ball at the player's last known position,
- // do so!
- return SCHED_COMBINE_AR2_ALTFIRE;
- }
-
- if( IsUsingTacticalVariant( TACTICAL_VARIANT_PRESSURE_ENEMY ) && !IsRunningBehavior() )
- {
- if( OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
- {
- return SCHED_COMBINE_PRESS_ATTACK;
- }
- }
-
- return SCHED_COMBINE_ESTABLISH_LINE_OF_FIRE;
- }
- break;
- case SCHED_HIDE_AND_RELOAD:
- {
- // stand up, just in case
- // Stand();
- // DesireStand();
- if( CanGrenadeEnemy() && OccupyStrategySlot( SQUAD_SLOT_GRENADE1 ) && random->RandomInt( 0, 100 ) < 20 )
- {
- // If I COULD throw a grenade and I need to reload, 20% chance I'll throw a grenade before I hide to reload.
- return SCHED_COMBINE_GRENADE_AND_RELOAD;
- }
-
- // No running away in the citadel!
- if ( ShouldChargePlayer() )
- return SCHED_RELOAD;
-
- return SCHED_COMBINE_HIDE_AND_RELOAD;
- }
- break;
- case SCHED_RANGE_ATTACK1:
- {
- if ( HasCondition( COND_NO_PRIMARY_AMMO ) || HasCondition( COND_LOW_PRIMARY_AMMO ) )
- {
- // Ditch the strategy slot for attacking (which we just reserved!)
- VacateStrategySlot();
- return TranslateSchedule( SCHED_HIDE_AND_RELOAD );
- }
-
- if( CanAltFireEnemy(true) && OccupyStrategySlot(SQUAD_SLOT_SPECIAL_ATTACK) )
- {
- // Since I'm holding this squadslot, no one else can try right now. If I die before the shot
- // goes off, I won't have affected anyone else's ability to use this attack at their nearest
- // convenience.
- return SCHED_COMBINE_AR2_ALTFIRE;
- }
-
- if ( IsCrouching() || ( CrouchIsDesired() && !HasCondition( COND_HEAVY_DAMAGE ) ) )
- {
- // See if we can crouch and shoot
- if (GetEnemy() != NULL)
- {
- float dist = (GetLocalOrigin() - GetEnemy()->GetLocalOrigin()).Length();
-
- // only crouch if they are relatively far away
- if (dist > COMBINE_MIN_CROUCH_DISTANCE)
- {
- // try crouching
- Crouch();
-
- Vector targetPos = GetEnemy()->BodyTarget(GetActiveWeapon()->GetLocalOrigin());
-
- // if we can't see it crouched, stand up
- if (!WeaponLOSCondition(GetLocalOrigin(),targetPos,false))
- {
- Stand();
- }
- }
- }
- }
- else
- {
- // always assume standing
- Stand();
- }
-
- return SCHED_COMBINE_RANGE_ATTACK1;
- }
- case SCHED_RANGE_ATTACK2:
- {
- // If my weapon can range attack 2 use the weapon
- if (GetActiveWeapon() && GetActiveWeapon()->CapabilitiesGet() & bits_CAP_WEAPON_RANGE_ATTACK2)
- {
- return SCHED_RANGE_ATTACK2;
- }
- // Otherwise use innate attack
- else
- {
- return SCHED_COMBINE_RANGE_ATTACK2;
- }
- }
- // SCHED_COMBAT_FACE:
- // SCHED_COMBINE_WAIT_FACE_ENEMY:
- // SCHED_COMBINE_SWEEP:
- // SCHED_COMBINE_COVER_AND_RELOAD:
- // SCHED_COMBINE_FOUND_ENEMY:
-
- case SCHED_VICTORY_DANCE:
- {
- return SCHED_COMBINE_VICTORY_DANCE;
- }
- case SCHED_COMBINE_SUPPRESS:
- {
-#define MIN_SIGNAL_DIST 256
- if ( GetEnemy() != NULL && GetEnemy()->IsPlayer() && m_bFirstEncounter )
- {
- float flDistToEnemy = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).Length();
-
- if( flDistToEnemy >= MIN_SIGNAL_DIST )
- {
- m_bFirstEncounter = false;// after first encounter, leader won't issue handsigns anymore when he has a new enemy
- return SCHED_COMBINE_SIGNAL_SUPPRESS;
- }
- }
-
- return SCHED_COMBINE_SUPPRESS;
- }
- case SCHED_FAIL:
- {
- if ( GetEnemy() != NULL )
- {
- return SCHED_COMBINE_COMBAT_FAIL;
- }
- return SCHED_FAIL;
- }
-
- case SCHED_COMBINE_PATROL:
- {
- // If I have an enemy, don't go off into random patrol mode.
- if ( GetEnemy() && GetEnemy()->IsAlive() )
- return SCHED_COMBINE_PATROL_ENEMY;
-
- return SCHED_COMBINE_PATROL;
- }
- }
-
- return BaseClass::TranslateSchedule( scheduleType );
-}
-
-//=========================================================
-//=========================================================
-void CNPC_Combine::OnStartSchedule( int scheduleType )
-{
-}
-
-//=========================================================
-// HandleAnimEvent - catches the monster-specific messages
-// that occur when tagged animation frames are played.
-//=========================================================
-void CNPC_Combine::HandleAnimEvent( animevent_t *pEvent )
-{
- Vector vecShootDir;
- Vector vecShootOrigin;
- bool handledEvent = false;
-
- if (pEvent->type & AE_TYPE_NEWEVENTSYSTEM)
- {
- if ( pEvent->event == COMBINE_AE_BEGIN_ALTFIRE )
- {
- EmitSound( "Weapon_CombineGuard.Special1" );
- handledEvent = true;
- }
- else if ( pEvent->event == COMBINE_AE_ALTFIRE )
- {
- if( IsElite() )
- {
- animevent_t fakeEvent;
-
- fakeEvent.pSource = this;
- fakeEvent.event = EVENT_WEAPON_AR2_ALTFIRE;
- GetActiveWeapon()->Operator_HandleAnimEvent( &fakeEvent, this );
-
- // Stop other squad members from combine balling for a while.
- DelaySquadAltFireAttack( 10.0f );
-
- // I'm disabling this decrementor. At the time of this change, the elites
- // don't bother to check if they have grenades anyway. This means that all
- // elites have infinite combine balls, even if the designer marks the elite
- // as having 0 grenades. By disabling this decrementor, yet enabling the code
- // that makes sure the elite has grenades in order to fire a combine ball, we
- // preserve the legacy behavior while making it possible for a designer to prevent
- // elites from shooting combine balls by setting grenades to '0' in hammer. (sjb) EP2_OUTLAND_10
- // m_iNumGrenades--;
- }
-
- handledEvent = true;
- }
- else
- {
- BaseClass::HandleAnimEvent( pEvent );
- }
- }
- else
- {
- switch( pEvent->event )
- {
- case COMBINE_AE_AIM:
- {
- handledEvent = true;
- break;
- }
- case COMBINE_AE_RELOAD:
-
- // We never actually run out of ammo, just need to refill the clip
- if (GetActiveWeapon())
- {
- GetActiveWeapon()->WeaponSound( RELOAD_NPC );
- GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1();
- GetActiveWeapon()->m_iClip2 = GetActiveWeapon()->GetMaxClip2();
- }
- ClearCondition(COND_LOW_PRIMARY_AMMO);
- ClearCondition(COND_NO_PRIMARY_AMMO);
- ClearCondition(COND_NO_SECONDARY_AMMO);
- handledEvent = true;
- break;
-
- case COMBINE_AE_GREN_TOSS:
- {
- Vector vecSpin;
- vecSpin.x = random->RandomFloat( -1000.0, 1000.0 );
- vecSpin.y = random->RandomFloat( -1000.0, 1000.0 );
- vecSpin.z = random->RandomFloat( -1000.0, 1000.0 );
-
- Vector vecStart;
- GetAttachment( "lefthand", vecStart );
-
- if( m_NPCState == NPC_STATE_SCRIPT )
- {
- // Use a fixed velocity for grenades thrown in scripted state.
- // Grenades thrown from a script do not count against grenades remaining for the AI to use.
- Vector forward, up, vecThrow;
-
- GetVectors( &forward, NULL, &up );
- vecThrow = forward * 750 + up * 175;
- Fraggrenade_Create( vecStart, vec3_angle, vecThrow, vecSpin, this, COMBINE_GRENADE_TIMER, true );
- }
- else
- {
- // Use the Velocity that AI gave us.
- Fraggrenade_Create( vecStart, vec3_angle, m_vecTossVelocity, vecSpin, this, COMBINE_GRENADE_TIMER, true );
- m_iNumGrenades--;
- }
-
- // wait six seconds before even looking again to see if a grenade can be thrown.
- m_flNextGrenadeCheck = gpGlobals->curtime + 6;
- }
- handledEvent = true;
- break;
-
- case COMBINE_AE_GREN_LAUNCH:
- {
- EmitSound( "NPC_Combine.GrenadeLaunch" );
-
- CBaseEntity *pGrenade = CreateNoSpawn( "npc_contactgrenade", Weapon_ShootPosition(), vec3_angle, this );
- pGrenade->KeyValue( "velocity", m_vecTossVelocity );
- pGrenade->Spawn( );
-
- if ( g_pGameRules->IsSkillLevel(SKILL_HARD) )
- m_flNextGrenadeCheck = gpGlobals->curtime + random->RandomFloat( 2, 5 );// wait a random amount of time before shooting again
- else
- m_flNextGrenadeCheck = gpGlobals->curtime + 6;// wait six seconds before even looking again to see if a grenade can be thrown.
- }
- handledEvent = true;
- break;
-
- case COMBINE_AE_GREN_DROP:
- {
- Vector vecStart;
- GetAttachment( "lefthand", vecStart );
-
- Fraggrenade_Create( vecStart, vec3_angle, m_vecTossVelocity, vec3_origin, this, COMBINE_GRENADE_TIMER, true );
- m_iNumGrenades--;
- }
- handledEvent = true;
- break;
-
- case COMBINE_AE_KICK:
- {
- // Does no damage, because damage is applied based upon whether the target can handle the interaction
- CBaseEntity *pHurt = CheckTraceHullAttack( 70, -Vector(16,16,18), Vector(16,16,18), 0, DMG_CLUB );
- CBaseCombatCharacter* pBCC = ToBaseCombatCharacter( pHurt );
- if (pBCC)
- {
- Vector forward, up;
- AngleVectors( GetLocalAngles(), &forward, NULL, &up );
-
- if ( !pBCC->DispatchInteraction( g_interactionCombineBash, NULL, this ) )
- {
- if ( pBCC->IsPlayer() )
- {
- pBCC->ViewPunch( QAngle(-12,-7,0) );
- pHurt->ApplyAbsVelocityImpulse( forward * 100 + up * 50 );
- }
-
- CTakeDamageInfo info( this, this, m_nKickDamage, DMG_CLUB );
- CalculateMeleeDamageForce( &info, forward, pBCC->GetAbsOrigin() );
- pBCC->TakeDamage( info );
-
- EmitSound( "NPC_Combine.WeaponBash" );
- }
- }
-
- m_Sentences.Speak( "COMBINE_KICK" );
- handledEvent = true;
- break;
- }
-
- case COMBINE_AE_CAUGHT_ENEMY:
- m_Sentences.Speak( "COMBINE_ALERT" );
- handledEvent = true;
- break;
-
- default:
- BaseClass::HandleAnimEvent( pEvent );
- break;
- }
- }
-
- if( handledEvent )
- {
- m_iLastAnimEventHandled = pEvent->event;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Get shoot position of BCC at an arbitrary position
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-Vector CNPC_Combine::Weapon_ShootPosition( )
-{
- bool bStanding = !IsCrouching();
- Vector right;
- GetVectors( NULL, &right, NULL );
-
- if ((CapabilitiesGet() & bits_CAP_DUCK) )
- {
- if ( IsCrouchedActivity( GetActivity() ) )
- {
- bStanding = false;
- }
- }
-
- // FIXME: rename this "estimated" since it's not based on animation
- // FIXME: the orientation won't be correct when testing from arbitary positions for arbitary angles
-
- if ( bStanding )
- {
- if( HasShotgun() )
- {
- return GetAbsOrigin() + COMBINE_SHOTGUN_STANDING_POSITION + right * 8;
- }
- else
- {
- return GetAbsOrigin() + COMBINE_GUN_STANDING_POSITION + right * 8;
- }
- }
- else
- {
- if( HasShotgun() )
- {
- return GetAbsOrigin() + COMBINE_SHOTGUN_CROUCHING_POSITION + right * 8;
- }
- else
- {
- return GetAbsOrigin() + COMBINE_GUN_CROUCHING_POSITION + right * 8;
- }
- }
-}
-
-
-//=========================================================
-// Speak Sentence - say your cued up sentence.
-//
-// Some grunt sentences (take cover and charge) rely on actually
-// being able to execute the intended action. It's really lame
-// when a grunt says 'COVER ME' and then doesn't move. The problem
-// is that the sentences were played when the decision to TRY
-// to move to cover was made. Now the sentence is played after
-// we know for sure that there is a valid path. The schedule
-// may still fail but in most cases, well after the grunt has
-// started moving.
-//=========================================================
-void CNPC_Combine::SpeakSentence( int sentenceType )
-{
- switch( sentenceType )
- {
- case 0: // assault
- AnnounceAssault();
- break;
-
- case 1: // Flanking the player
- // If I'm moving more than 20ft, I need to talk about it
- if ( GetNavigator()->GetPath()->GetPathLength() > 20 * 12.0f )
- {
- m_Sentences.Speak( "COMBINE_FLANK" );
- }
- break;
- }
-}
-
-//=========================================================
-// PainSound
-//=========================================================
-void CNPC_Combine::PainSound ( void )
-{
- // NOTE: The response system deals with this at the moment
- if ( GetFlags() & FL_DISSOLVING )
- return;
-
- if ( gpGlobals->curtime > m_flNextPainSoundTime )
- {
- const char *pSentenceName = "COMBINE_PAIN";
- float healthRatio = (float)GetHealth() / (float)GetMaxHealth();
- if ( !HasMemory(bits_MEMORY_PAIN_LIGHT_SOUND) && healthRatio > 0.9 )
- {
- Remember( bits_MEMORY_PAIN_LIGHT_SOUND );
- pSentenceName = "COMBINE_TAUNT";
- }
- else if ( !HasMemory(bits_MEMORY_PAIN_HEAVY_SOUND) && healthRatio < 0.25 )
- {
- Remember( bits_MEMORY_PAIN_HEAVY_SOUND );
- pSentenceName = "COMBINE_COVER";
- }
-
- m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS );
- m_flNextPainSoundTime = gpGlobals->curtime + 1;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: implemented by subclasses to give them an opportunity to make
-// a sound when they lose their enemy
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-void CNPC_Combine::LostEnemySound( void)
-{
- if ( gpGlobals->curtime <= m_flNextLostSoundTime )
- return;
-
- const char *pSentence;
- if (!(CBaseEntity*)GetEnemy() || gpGlobals->curtime - GetEnemyLastTimeSeen() > 10)
- {
- pSentence = "COMBINE_LOST_LONG";
- }
- else
- {
- pSentence = "COMBINE_LOST_SHORT";
- }
-
- if ( m_Sentences.Speak( pSentence ) >= 0 )
- {
- m_flNextLostSoundTime = gpGlobals->curtime + random->RandomFloat(5.0,15.0);
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: implemented by subclasses to give them an opportunity to make
-// a sound when they lose their enemy
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-void CNPC_Combine::FoundEnemySound( void)
-{
- m_Sentences.Speak( "COMBINE_REFIND_ENEMY", SENTENCE_PRIORITY_HIGH );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Implemented by subclasses to give them an opportunity to make
-// a sound before they attack
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-
-// BUGBUG: It looks like this is never played because combine don't do SCHED_WAKE_ANGRY or anything else that does a TASK_SOUND_WAKE
-void CNPC_Combine::AlertSound( void)
-{
- if ( gpGlobals->curtime > m_flNextAlertSoundTime )
- {
- m_Sentences.Speak( "COMBINE_GO_ALERT", SENTENCE_PRIORITY_HIGH );
- m_flNextAlertSoundTime = gpGlobals->curtime + 10.0f;
- }
-}
-
-//=========================================================
-// NotifyDeadFriend
-//=========================================================
-void CNPC_Combine::NotifyDeadFriend ( CBaseEntity* pFriend )
-{
- if ( GetSquad()->NumMembers() < 2 )
- {
- m_Sentences.Speak( "COMBINE_LAST_OF_SQUAD", SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_NORMAL );
- JustMadeSound();
- return;
- }
- // relaxed visibility test so that guys say this more often
- //if( FInViewCone( pFriend ) && FVisible( pFriend ) )
- {
- m_Sentences.Speak( "COMBINE_MAN_DOWN" );
- }
- BaseClass::NotifyDeadFriend(pFriend);
-}
-
-//=========================================================
-// DeathSound
-//=========================================================
-void CNPC_Combine::DeathSound ( void )
-{
- // NOTE: The response system deals with this at the moment
- if ( GetFlags() & FL_DISSOLVING )
- return;
-
- m_Sentences.Speak( "COMBINE_DIE", SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS );
-}
-
-//=========================================================
-// IdleSound
-//=========================================================
-void CNPC_Combine::IdleSound( void )
-{
- if (g_fCombineQuestion || random->RandomInt(0,1))
- {
- if (!g_fCombineQuestion)
- {
- // ask question or make statement
- switch (random->RandomInt(0,2))
- {
- case 0: // check in
- if ( m_Sentences.Speak( "COMBINE_CHECK" ) >= 0 )
- {
- g_fCombineQuestion = 1;
- }
- break;
-
- case 1: // question
- if ( m_Sentences.Speak( "COMBINE_QUEST" ) >= 0 )
- {
- g_fCombineQuestion = 2;
- }
- break;
-
- case 2: // statement
- m_Sentences.Speak( "COMBINE_IDLE" );
- break;
- }
- }
- else
- {
- switch (g_fCombineQuestion)
- {
- case 1: // check in
- if ( m_Sentences.Speak( "COMBINE_CLEAR" ) >= 0 )
- {
- g_fCombineQuestion = 0;
- }
- break;
- case 2: // question
- if ( m_Sentences.Speak( "COMBINE_ANSWER" ) >= 0 )
- {
- g_fCombineQuestion = 0;
- }
- break;
- }
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//
-// This is for Grenade attacks. As the test for grenade attacks
-// is expensive we don't want to do it every frame. Return true
-// if we meet minimum set of requirements and then test for actual
-// throw later if we actually decide to do a grenade attack.
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-int CNPC_Combine::RangeAttack2Conditions( float flDot, float flDist )
-{
- return COND_NONE;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Return true if the combine has grenades, hasn't checked lately, and
-// can throw a grenade at the target point.
-// Input : &vecTarget -
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CNPC_Combine::CanThrowGrenade( const Vector &vecTarget )
-{
- if( m_iNumGrenades < 1 )
- {
- // Out of grenades!
- return false;
- }
-
- if (gpGlobals->curtime < m_flNextGrenadeCheck )
- {
- // Not allowed to throw another grenade right now.
- return false;
- }
-
- float flDist;
- flDist = ( vecTarget - GetAbsOrigin() ).Length();
-
- if( flDist > 1024 || flDist < 128 )
- {
- // Too close or too far!
- m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second.
- return false;
- }
-
- // -----------------------
- // If moving, don't check.
- // -----------------------
- if ( m_flGroundSpeed != 0 )
- return false;
-
-#if 0
- Vector vecEnemyLKP = GetEnemyLKP();
- if ( !( GetEnemy()->GetFlags() & FL_ONGROUND ) && GetEnemy()->GetWaterLevel() == 0 && vecEnemyLKP.z > (GetAbsOrigin().z + WorldAlignMaxs().z) )
- {
- //!!!BUGBUG - we should make this check movetype and make sure it isn't FLY? Players who jump a lot are unlikely to
- // be grenaded.
- // don't throw grenades at anything that isn't on the ground!
- return COND_NONE;
- }
-#endif
-
- // ---------------------------------------------------------------------
- // Are any of my squad members near the intended grenade impact area?
- // ---------------------------------------------------------------------
- if ( m_pSquad )
- {
- if (m_pSquad->SquadMemberInRange( vecTarget, COMBINE_MIN_GRENADE_CLEAR_DIST ))
- {
- // crap, I might blow my own guy up. Don't throw a grenade and don't check again for a while.
- m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second.
-
- // Tell my squad members to clear out so I can get a grenade in
- CSoundEnt::InsertSound( SOUND_MOVE_AWAY | SOUND_CONTEXT_COMBINE_ONLY, vecTarget, COMBINE_MIN_GRENADE_CLEAR_DIST, 0.1 );
- return false;
- }
- }
-
- return CheckCanThrowGrenade( vecTarget );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns true if the combine can throw a grenade at the specified target point
-// Input : &vecTarget -
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CNPC_Combine::CheckCanThrowGrenade( const Vector &vecTarget )
-{
- //NDebugOverlay::Line( EyePosition(), vecTarget, 0, 255, 0, false, 5 );
-
- // ---------------------------------------------------------------------
- // Check that throw is legal and clear
- // ---------------------------------------------------------------------
- // FIXME: this is only valid for hand grenades, not RPG's
- Vector vecToss;
- Vector vecMins = -Vector(4,4,4);
- Vector vecMaxs = Vector(4,4,4);
- if( FInViewCone( vecTarget ) && CBaseEntity::FVisible( vecTarget ) )
- {
- vecToss = VecCheckThrow( this, EyePosition(), vecTarget, COMBINE_GRENADE_THROW_SPEED, 1.0, &vecMins, &vecMaxs );
- }
- else
- {
- // Have to try a high toss. Do I have enough room?
- trace_t tr;
- AI_TraceLine( EyePosition(), EyePosition() + Vector( 0, 0, 64 ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
- if( tr.fraction != 1.0 )
- {
- return false;
- }
-
- vecToss = VecCheckToss( this, EyePosition(), vecTarget, -1, 1.0, true, &vecMins, &vecMaxs );
- }
-
- if ( vecToss != vec3_origin )
- {
- m_vecTossVelocity = vecToss;
-
- // don't check again for a while.
- m_flNextGrenadeCheck = gpGlobals->curtime + 1; // 1/3 second.
- return true;
- }
- else
- {
- // don't check again for a while.
- m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second.
- return false;
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Combine::CanAltFireEnemy( bool bUseFreeKnowledge )
-{
- if (!IsElite() )
- return false;
-
- if (IsCrouching())
- return false;
-
- if( gpGlobals->curtime < m_flNextAltFireTime )
- return false;
-
- if( !GetEnemy() )
- return false;
-
- if (gpGlobals->curtime < m_flNextGrenadeCheck )
- return false;
-
- // See Steve Bond if you plan on changing this next piece of code!! (SJB) EP2_OUTLAND_10
- if (m_iNumGrenades < 1)
- return false;
-
- CBaseEntity *pEnemy = GetEnemy();
-
- if( !pEnemy->IsPlayer() && (!pEnemy->IsNPC() || !pEnemy->MyNPCPointer()->IsPlayerAlly()) )
- return false;
-
- Vector vecTarget;
-
- // Determine what point we're shooting at
- if( bUseFreeKnowledge )
- {
- vecTarget = GetEnemies()->LastKnownPosition( pEnemy ) + (pEnemy->GetViewOffset()*0.75);// approximates the chest
- }
- else
- {
- vecTarget = GetEnemies()->LastSeenPosition( pEnemy ) + (pEnemy->GetViewOffset()*0.75);// approximates the chest
- }
-
- // Trace a hull about the size of the combine ball (don't shoot through grates!)
- trace_t tr;
-
- Vector mins( -12, -12, -12 );
- Vector maxs( 12, 12, 12 );
-
- Vector vShootPosition = EyePosition();
-
- if ( GetActiveWeapon() )
- {
- GetActiveWeapon()->GetAttachment( "muzzle", vShootPosition );
- }
-
- // Trace a hull about the size of the combine ball.
- UTIL_TraceHull( vShootPosition, vecTarget, mins, maxs, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
-
- float flLength = (vShootPosition - vecTarget).Length();
-
- flLength *= tr.fraction;
-
- //If the ball can travel at least 65% of the distance to the player then let the NPC shoot it.
- if( tr.fraction >= 0.65 && flLength > 128.0f )
- {
- // Target is valid
- m_vecAltFireTarget = vecTarget;
- return true;
- }
-
-
- // Check again later
- m_vecAltFireTarget = vec3_origin;
- m_flNextGrenadeCheck = gpGlobals->curtime + 1.0f;
- return false;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Combine::CanGrenadeEnemy( bool bUseFreeKnowledge )
-{
- if( IsElite() )
- return false;
-
- CBaseEntity *pEnemy = GetEnemy();
-
- Assert( pEnemy != NULL );
-
- if( pEnemy )
- {
- // I'm not allowed to throw grenades during dustoff
- if ( IsCurSchedule(SCHED_DROPSHIP_DUSTOFF) )
- return false;
-
- if( bUseFreeKnowledge )
- {
- // throw to where we think they are.
- return CanThrowGrenade( GetEnemies()->LastKnownPosition( pEnemy ) );
- }
- else
- {
- // hafta throw to where we last saw them.
- return CanThrowGrenade( GetEnemies()->LastSeenPosition( pEnemy ) );
- }
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: For combine melee attack (kick/hit)
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-int CNPC_Combine::MeleeAttack1Conditions ( float flDot, float flDist )
-{
- if (flDist > 64)
- {
- return COND_NONE; // COND_TOO_FAR_TO_ATTACK;
- }
- else if (flDot < 0.7)
- {
- return COND_NONE; // COND_NOT_FACING_ATTACK;
- }
-
- // Check Z
- if ( GetEnemy() && fabs(GetEnemy()->GetAbsOrigin().z - GetAbsOrigin().z) > 64 )
- return COND_NONE;
-
- if ( dynamic_cast<CBaseHeadcrab *>(GetEnemy()) != NULL )
- {
- return COND_NONE;
- }
-
- // Make sure not trying to kick through a window or something.
- trace_t tr;
- Vector vecSrc, vecEnd;
-
- vecSrc = WorldSpaceCenter();
- vecEnd = GetEnemy()->WorldSpaceCenter();
-
- AI_TraceLine(vecSrc, vecEnd, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
- if( tr.m_pEnt != GetEnemy() )
- {
- return COND_NONE;
- }
-
- return COND_CAN_MELEE_ATTACK1;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : Vector
-//-----------------------------------------------------------------------------
-Vector CNPC_Combine::EyePosition( void )
-{
- if ( !IsCrouching() )
- {
- return GetAbsOrigin() + COMBINE_EYE_STANDING_POSITION;
- }
- else
- {
- return GetAbsOrigin() + COMBINE_EYE_CROUCHING_POSITION;
- }
-
- /*
- Vector m_EyePos;
- GetAttachment( "eyes", m_EyePos );
- return m_EyePos;
- */
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-Vector CNPC_Combine::GetAltFireTarget()
-{
- Assert( IsElite() );
-
- return m_vecAltFireTarget;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : nActivity -
-// Output : Vector
-//-----------------------------------------------------------------------------
-Vector CNPC_Combine::EyeOffset( Activity nActivity )
-{
- if (CapabilitiesGet() & bits_CAP_DUCK)
- {
- if ( IsCrouchedActivity( nActivity ) )
- return COMBINE_EYE_CROUCHING_POSITION;
-
- }
- // if the hint doesn't tell anything, assume current state
- if ( !IsCrouching() )
- {
- return COMBINE_EYE_STANDING_POSITION;
- }
- else
- {
- return COMBINE_EYE_CROUCHING_POSITION;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-Vector CNPC_Combine::GetCrouchEyeOffset( void )
-{
- return COMBINE_EYE_CROUCHING_POSITION;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Combine::SetActivity( Activity NewActivity )
-{
- BaseClass::SetActivity( NewActivity );
-
- m_iLastAnimEventHandled = -1;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-NPC_STATE CNPC_Combine::SelectIdealState( void )
-{
- switch ( m_NPCState )
- {
- case NPC_STATE_COMBAT:
- {
- if ( GetEnemy() == NULL )
- {
- if ( !HasCondition( COND_ENEMY_DEAD ) )
- {
- // Lost track of my enemy. Patrol.
- SetCondition( COND_COMBINE_SHOULD_PATROL );
- }
- return NPC_STATE_ALERT;
- }
- else if ( HasCondition( COND_ENEMY_DEAD ) )
- {
- AnnounceEnemyKill(GetEnemy());
- }
- }
-
- default:
- {
- return BaseClass::SelectIdealState();
- }
- }
-
- return GetIdealState();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Combine::OnBeginMoveAndShoot()
-{
- if ( BaseClass::OnBeginMoveAndShoot() )
- {
- if( HasStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
- return true; // already have the slot I need
-
- if( !HasStrategySlotRange( SQUAD_SLOT_GRENADE1, SQUAD_SLOT_ATTACK_OCCLUDER ) && OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
- return true;
- }
- return false;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Combine::OnEndMoveAndShoot()
-{
- VacateStrategySlot();
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-WeaponProficiency_t CNPC_Combine::CalcWeaponProficiency( CBaseCombatWeapon *pWeapon )
-{
- if( FClassnameIs( pWeapon, "weapon_ar2" ) )
- {
- if( hl2_episodic.GetBool() )
- {
- return WEAPON_PROFICIENCY_VERY_GOOD;
- }
- else
- {
- return WEAPON_PROFICIENCY_GOOD;
- }
- }
- else if( FClassnameIs( pWeapon, "weapon_shotgun" ) )
- {
- if( m_nSkin != COMBINE_SKIN_SHOTGUNNER )
- {
- m_nSkin = COMBINE_SKIN_SHOTGUNNER;
- }
-
- return WEAPON_PROFICIENCY_PERFECT;
- }
- else if( FClassnameIs( pWeapon, "weapon_smg1" ) )
- {
- return WEAPON_PROFICIENCY_GOOD;
- }
-
- return BaseClass::CalcWeaponProficiency( pWeapon );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Combine::HasShotgun()
-{
- if( GetActiveWeapon() && GetActiveWeapon()->m_iClassname == s_iszShotgunClassname )
- {
- return true;
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Only supports weapons that use clips.
-//-----------------------------------------------------------------------------
-bool CNPC_Combine::ActiveWeaponIsFullyLoaded()
-{
- CBaseCombatWeapon *pWeapon = GetActiveWeapon();
-
- if( !pWeapon )
- return false;
-
- if( !pWeapon->UsesClipsForAmmo1() )
- return false;
-
- return ( pWeapon->Clip1() >= pWeapon->GetMaxClip1() );
-}
-
-
-//-----------------------------------------------------------------------------
-// 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 : The type of interaction, extra info pointer, and who started it
-// Output : true - if sub-class has a response for the interaction
-// false - if sub-class has no response
-//-----------------------------------------------------------------------------
-bool CNPC_Combine::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter *sourceEnt)
-{
- if ( interactionType == g_interactionTurretStillStanding )
- {
- // A turret that I've kicked recently is still standing 5 seconds later.
- if ( sourceEnt == GetEnemy() )
- {
- // It's still my enemy. Time to grenade it.
- Vector forward, up;
- AngleVectors( GetLocalAngles(), &forward, NULL, &up );
- m_vecTossVelocity = forward * 10;
- SetCondition( COND_COMBINE_DROP_GRENADE );
- ClearSchedule( "Failed to kick over turret" );
- }
- return true;
- }
-
- return BaseClass::HandleInteraction( interactionType, data, sourceEnt );
-}
-
-//-----------------------------------------------------------------------------
-//
-//-----------------------------------------------------------------------------
-const char* CNPC_Combine::GetSquadSlotDebugName( int iSquadSlot )
-{
- switch( iSquadSlot )
- {
- case SQUAD_SLOT_GRENADE1: return "SQUAD_SLOT_GRENADE1";
- break;
- case SQUAD_SLOT_GRENADE2: return "SQUAD_SLOT_GRENADE2";
- break;
- case SQUAD_SLOT_ATTACK_OCCLUDER: return "SQUAD_SLOT_ATTACK_OCCLUDER";
- break;
- case SQUAD_SLOT_OVERWATCH: return "SQUAD_SLOT_OVERWATCH";
- break;
- }
-
- return BaseClass::GetSquadSlotDebugName( iSquadSlot );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Combine::IsUsingTacticalVariant( int variant )
-{
- if( variant == TACTICAL_VARIANT_PRESSURE_ENEMY && m_iTacticalVariant == TACTICAL_VARIANT_PRESSURE_ENEMY_UNTIL_CLOSE )
- {
- // Essentially, fib. Just say that we are a 'pressure enemy' soldier.
- return true;
- }
-
- return m_iTacticalVariant == variant;
-}
-
-//-----------------------------------------------------------------------------
-// For the purpose of determining whether to use a pathfinding variant, this
-// function determines whether the current schedule is a schedule that
-// 'approaches' the enemy.
-//-----------------------------------------------------------------------------
-bool CNPC_Combine::IsRunningApproachEnemySchedule()
-{
- if( IsCurSchedule( SCHED_CHASE_ENEMY ) )
- return true;
-
- if( IsCurSchedule( SCHED_ESTABLISH_LINE_OF_FIRE ) )
- return true;
-
- if( IsCurSchedule( SCHED_COMBINE_PRESS_ATTACK, false ) )
- return true;
-
- return false;
-}
-
-bool CNPC_Combine::ShouldPickADeathPose( void )
-{
- return !IsCrouching();
-}
-
-//-----------------------------------------------------------------------------
-//
-// Schedules
-//
-//-----------------------------------------------------------------------------
-
-AI_BEGIN_CUSTOM_NPC( npc_combine, CNPC_Combine )
-
-//Tasks
-DECLARE_TASK( TASK_COMBINE_FACE_TOSS_DIR )
-DECLARE_TASK( TASK_COMBINE_IGNORE_ATTACKS )
-DECLARE_TASK( TASK_COMBINE_SIGNAL_BEST_SOUND )
-DECLARE_TASK( TASK_COMBINE_DEFER_SQUAD_GRENADES )
-DECLARE_TASK( TASK_COMBINE_CHASE_ENEMY_CONTINUOUSLY )
-DECLARE_TASK( TASK_COMBINE_DIE_INSTANTLY )
-DECLARE_TASK( TASK_COMBINE_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET )
-DECLARE_TASK( TASK_COMBINE_GET_PATH_TO_FORCED_GREN_LOS )
-DECLARE_TASK( TASK_COMBINE_SET_STANDING )
-
-//Activities
-DECLARE_ACTIVITY( ACT_COMBINE_THROW_GRENADE )
-DECLARE_ACTIVITY( ACT_COMBINE_LAUNCH_GRENADE )
-DECLARE_ACTIVITY( ACT_COMBINE_BUGBAIT )
-DECLARE_ACTIVITY( ACT_COMBINE_AR2_ALTFIRE )
-DECLARE_ACTIVITY( ACT_WALK_EASY )
-DECLARE_ACTIVITY( ACT_WALK_MARCH )
-
-DECLARE_ANIMEVENT( COMBINE_AE_BEGIN_ALTFIRE )
-DECLARE_ANIMEVENT( COMBINE_AE_ALTFIRE )
-
-DECLARE_SQUADSLOT( SQUAD_SLOT_GRENADE1 )
-DECLARE_SQUADSLOT( SQUAD_SLOT_GRENADE2 )
-
-DECLARE_CONDITION( COND_COMBINE_NO_FIRE )
-DECLARE_CONDITION( COND_COMBINE_DEAD_FRIEND )
-DECLARE_CONDITION( COND_COMBINE_SHOULD_PATROL )
-DECLARE_CONDITION( COND_COMBINE_HIT_BY_BUGBAIT )
-DECLARE_CONDITION( COND_COMBINE_DROP_GRENADE )
-DECLARE_CONDITION( COND_COMBINE_ON_FIRE )
-DECLARE_CONDITION( COND_COMBINE_ATTACK_SLOT_AVAILABLE )
-
-DECLARE_INTERACTION( g_interactionCombineBash );
-
-//=========================================================
-// SCHED_COMBINE_TAKE_COVER_FROM_BEST_SOUND
-//
-// hide from the loudest sound source (to run from grenade)
-//=========================================================
-DEFINE_SCHEDULE
-(
- SCHED_COMBINE_TAKE_COVER_FROM_BEST_SOUND,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBINE_RUN_AWAY_FROM_BEST_SOUND"
- " TASK_STOP_MOVING 0"
- " TASK_COMBINE_SIGNAL_BEST_SOUND 0"
- " TASK_FIND_COVER_FROM_BEST_SOUND 0"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_REMEMBER MEMORY:INCOVER"
- " TASK_FACE_REASONABLE 0"
- ""
- " Interrupts"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_RUN_AWAY_FROM_BEST_SOUND,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COWER"
- " TASK_GET_PATH_AWAY_FROM_BEST_SOUND 600"
- " TASK_RUN_PATH_TIMED 2"
- " TASK_STOP_MOVING 0"
- ""
- " Interrupts"
- )
- //=========================================================
- // SCHED_COMBINE_COMBAT_FAIL
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_COMBAT_FAIL,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE "
- " TASK_WAIT_FACE_ENEMY 2"
- " TASK_WAIT_PVS 0"
- ""
- " Interrupts"
- " COND_CAN_RANGE_ATTACK1"
- " COND_CAN_RANGE_ATTACK2"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- )
-
- //=========================================================
- // SCHED_COMBINE_VICTORY_DANCE
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_VICTORY_DANCE,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_ENEMY 0"
- " TASK_WAIT 1.5"
- " TASK_GET_PATH_TO_ENEMY_CORPSE 0"
- " TASK_WALK_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_FACE_ENEMY 0"
- " TASK_PLAY_SEQUENCE ACTIVITY:ACT_VICTORY_DANCE"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- )
-
- //=========================================================
- // SCHED_COMBINE_ASSAULT
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_ASSAULT,
-
- " Tasks "
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBINE_ESTABLISH_LINE_OF_FIRE"
- " TASK_SET_TOLERANCE_DISTANCE 48"
- " TASK_GET_PATH_TO_ENEMY_LKP 0"
- " TASK_COMBINE_IGNORE_ATTACKS 0.2"
- " TASK_SPEAK_SENTENCE 0"
- " TASK_RUN_PATH 0"
- // " TASK_COMBINE_MOVE_AND_AIM 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_COMBINE_IGNORE_ATTACKS 0.0"
- ""
- " Interrupts "
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_ENEMY_UNREACHABLE"
- " COND_CAN_RANGE_ATTACK1"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_RANGE_ATTACK2"
- " COND_CAN_MELEE_ATTACK2"
- " COND_TOO_FAR_TO_ATTACK"
- " COND_HEAR_DANGER"
- " COND_HEAR_MOVE_AWAY"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_ESTABLISH_LINE_OF_FIRE,
-
- " Tasks "
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_FAIL_ESTABLISH_LINE_OF_FIRE"
- " TASK_SET_TOLERANCE_DISTANCE 48"
- " TASK_GET_PATH_TO_ENEMY_LKP_LOS 0"
- " TASK_COMBINE_SET_STANDING 1"
- " TASK_SPEAK_SENTENCE 1"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_COMBINE_IGNORE_ATTACKS 0.0"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_COMBAT_FACE"
- " "
- " Interrupts "
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- //" COND_CAN_RANGE_ATTACK1"
- //" COND_CAN_RANGE_ATTACK2"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- " COND_HEAR_DANGER"
- " COND_HEAR_MOVE_AWAY"
- " COND_HEAVY_DAMAGE"
- )
-
- //=========================================================
- // SCHED_COMBINE_PRESS_ATTACK
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_PRESS_ATTACK,
-
- " Tasks "
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBINE_ESTABLISH_LINE_OF_FIRE"
- " TASK_SET_TOLERANCE_DISTANCE 72"
- " TASK_GET_PATH_TO_ENEMY_LKP 0"
- " TASK_COMBINE_SET_STANDING 1"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- ""
- " Interrupts "
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_ENEMY_UNREACHABLE"
- " COND_NO_PRIMARY_AMMO"
- " COND_LOW_PRIMARY_AMMO"
- " COND_TOO_CLOSE_TO_ATTACK"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- " COND_HEAR_DANGER"
- " COND_HEAR_MOVE_AWAY"
- )
-
- //=========================================================
- // SCHED_COMBINE_COMBAT_FACE
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_COMBAT_FACE,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_FACE_ENEMY 0"
- " TASK_WAIT 1.5"
- //" TASK_SET_SCHEDULE SCHEDULE:SCHED_COMBINE_SWEEP"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_CAN_RANGE_ATTACK1"
- " COND_CAN_RANGE_ATTACK2"
- )
-
- //=========================================================
- // SCHED_HIDE_AND_RELOAD
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_HIDE_AND_RELOAD,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_RELOAD"
- " TASK_FIND_COVER_FROM_ENEMY 0"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_REMEMBER MEMORY:INCOVER"
- " TASK_FACE_ENEMY 0"
- " TASK_RELOAD 0"
- ""
- " Interrupts"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- " COND_HEAVY_DAMAGE"
- " COND_HEAR_DANGER"
- " COND_HEAR_MOVE_AWAY"
- )
-
- //=========================================================
- // SCHED_COMBINE_SIGNAL_SUPPRESS
- // don't stop shooting until the clip is
- // empty or combine gets hurt.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_SIGNAL_SUPPRESS,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_IDEAL 0"
- " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_SIGNAL_GROUP"
- " TASK_COMBINE_SET_STANDING 0"
- " TASK_RANGE_ATTACK1 0"
- ""
- " Interrupts"
- " COND_ENEMY_DEAD"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- " COND_NO_PRIMARY_AMMO"
- " COND_WEAPON_BLOCKED_BY_FRIEND"
- " COND_WEAPON_SIGHT_OCCLUDED"
- " COND_HEAR_DANGER"
- " COND_HEAR_MOVE_AWAY"
- " COND_COMBINE_NO_FIRE"
- )
-
- //=========================================================
- // SCHED_COMBINE_SUPPRESS
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_SUPPRESS,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_ENEMY 0"
- " TASK_COMBINE_SET_STANDING 0"
- " TASK_RANGE_ATTACK1 0"
- ""
- " Interrupts"
- " COND_ENEMY_DEAD"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- " COND_NO_PRIMARY_AMMO"
- " COND_HEAR_DANGER"
- " COND_HEAR_MOVE_AWAY"
- " COND_COMBINE_NO_FIRE"
- " COND_WEAPON_BLOCKED_BY_FRIEND"
- )
-
- //=========================================================
- // SCHED_COMBINE_ENTER_OVERWATCH
- //
- // Parks a combine soldier in place looking at the player's
- // last known position, ready to attack if the player pops out
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_ENTER_OVERWATCH,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_COMBINE_SET_STANDING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_FACE_ENEMY 0"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_COMBINE_OVERWATCH"
- ""
- " Interrupts"
- " COND_HEAR_DANGER"
- " COND_NEW_ENEMY"
- )
-
- //=========================================================
- // SCHED_COMBINE_OVERWATCH
- //
- // Parks a combine soldier in place looking at the player's
- // last known position, ready to attack if the player pops out
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_OVERWATCH,
-
- " Tasks"
- " TASK_WAIT_FACE_ENEMY 10"
- ""
- " Interrupts"
- " COND_CAN_RANGE_ATTACK1"
- " COND_ENEMY_DEAD"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- " COND_NO_PRIMARY_AMMO"
- " COND_HEAR_DANGER"
- " COND_HEAR_MOVE_AWAY"
- " COND_NEW_ENEMY"
- )
-
- //=========================================================
- // SCHED_COMBINE_WAIT_IN_COVER
- // we don't allow danger or the ability
- // to attack to break a combine's run to cover schedule but
- // when a combine is in cover we do want them to attack if they can.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_WAIT_IN_COVER,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_COMBINE_SET_STANDING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Translated to cover
- " TASK_WAIT_FACE_ENEMY 1"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_CAN_RANGE_ATTACK1"
- " COND_CAN_RANGE_ATTACK2"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- " COND_HEAR_DANGER"
- " COND_HEAR_MOVE_AWAY"
- " COND_COMBINE_ATTACK_SLOT_AVAILABLE"
- )
-
- //=========================================================
- // SCHED_COMBINE_TAKE_COVER1
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_TAKE_COVER1 ,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBINE_TAKECOVER_FAILED"
- " TASK_STOP_MOVING 0"
- " TASK_WAIT 0.2"
- " TASK_FIND_COVER_FROM_ENEMY 0"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_REMEMBER MEMORY:INCOVER"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_COMBINE_WAIT_IN_COVER"
- ""
- " Interrupts"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_TAKECOVER_FAILED,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- ""
- " Interrupts"
- )
-
- //=========================================================
- // SCHED_COMBINE_GRENADE_COVER1
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_GRENADE_COVER1,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_FIND_COVER_FROM_ENEMY 99"
- " TASK_FIND_FAR_NODE_COVER_FROM_ENEMY 384"
- " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SPECIAL_ATTACK2"
- " TASK_CLEAR_MOVE_WAIT 0"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_COMBINE_WAIT_IN_COVER"
- ""
- " Interrupts"
- )
-
- //=========================================================
- // SCHED_COMBINE_TOSS_GRENADE_COVER1
- //
- // drop grenade then run to cover.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_TOSS_GRENADE_COVER1,
-
- " Tasks"
- " TASK_FACE_ENEMY 0"
- " TASK_RANGE_ATTACK2 0"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_TAKE_COVER_FROM_ENEMY"
- ""
- " Interrupts"
- )
-
- //=========================================================
- // SCHED_COMBINE_RANGE_ATTACK1
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_RANGE_ATTACK1,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_ENEMY 0"
- " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
- " TASK_WAIT_RANDOM 0.3"
- " TASK_RANGE_ATTACK1 0"
- " TASK_COMBINE_IGNORE_ATTACKS 0.5"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_HEAVY_DAMAGE"
- " COND_LIGHT_DAMAGE"
- " COND_LOW_PRIMARY_AMMO"
- " COND_NO_PRIMARY_AMMO"
- " COND_WEAPON_BLOCKED_BY_FRIEND"
- " COND_TOO_CLOSE_TO_ATTACK"
- " COND_GIVE_WAY"
- " COND_HEAR_DANGER"
- " COND_HEAR_MOVE_AWAY"
- " COND_COMBINE_NO_FIRE"
- ""
- // Enemy_Occluded Don't interrupt on this. Means
- // comibine will fire where player was after
- // he has moved for a little while. Good effect!!
- // WEAPON_SIGHT_OCCLUDED Don't block on this! Looks better for railings, etc.
- )
-
- //=========================================================
- // AR2 Alt Fire Attack
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_AR2_ALTFIRE,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_ANNOUNCE_ATTACK 1"
- " TASK_COMBINE_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET ACTIVITY:ACT_COMBINE_AR2_ALTFIRE"
- ""
- " Interrupts"
- )
-
- //=========================================================
- // Mapmaker forced grenade throw
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_FORCED_GRENADE_THROW,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_COMBINE_FACE_TOSS_DIR 0"
- " TASK_ANNOUNCE_ATTACK 2" // 2 = grenade
- " TASK_PLAY_SEQUENCE ACTIVITY:ACT_RANGE_ATTACK2"
- " TASK_COMBINE_DEFER_SQUAD_GRENADES 0"
- ""
- " Interrupts"
- )
-
- //=========================================================
- // Move to LOS of the mapmaker's forced grenade throw target
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_MOVE_TO_FORCED_GREN_LOS,
-
- " Tasks "
- " TASK_SET_TOLERANCE_DISTANCE 48"
- " TASK_COMBINE_GET_PATH_TO_FORCED_GREN_LOS 0"
- " TASK_SPEAK_SENTENCE 1"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " "
- " Interrupts "
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- " COND_HEAR_DANGER"
- " COND_HEAR_MOVE_AWAY"
- " COND_HEAVY_DAMAGE"
- )
-
- //=========================================================
- // SCHED_COMBINE_RANGE_ATTACK2
- //
- // secondary range attack. Overriden because base class stops attacking when the enemy is occluded.
- // combines's grenade toss requires the enemy be occluded.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_RANGE_ATTACK2,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_COMBINE_FACE_TOSS_DIR 0"
- " TASK_ANNOUNCE_ATTACK 2" // 2 = grenade
- " TASK_PLAY_SEQUENCE ACTIVITY:ACT_RANGE_ATTACK2"
- " TASK_COMBINE_DEFER_SQUAD_GRENADES 0"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_COMBINE_WAIT_IN_COVER" // don't run immediately after throwing grenade.
- ""
- " Interrupts"
- )
-
-
- //=========================================================
- // Throw a grenade, then run off and reload.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_GRENADE_AND_RELOAD,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_COMBINE_FACE_TOSS_DIR 0"
- " TASK_ANNOUNCE_ATTACK 2" // 2 = grenade
- " TASK_PLAY_SEQUENCE ACTIVITY:ACT_RANGE_ATTACK2"
- " TASK_COMBINE_DEFER_SQUAD_GRENADES 0"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_HIDE_AND_RELOAD" // don't run immediately after throwing grenade.
- ""
- " Interrupts"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_PATROL,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_WANDER 900540"
- " TASK_WALK_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_REASONABLE 0"
- " TASK_WAIT 3"
- " TASK_WAIT_RANDOM 3"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_COMBINE_PATROL" // keep doing it
- ""
- " Interrupts"
- " COND_ENEMY_DEAD"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- " COND_HEAR_DANGER"
- " COND_HEAR_MOVE_AWAY"
- " COND_NEW_ENEMY"
- " COND_SEE_ENEMY"
- " COND_CAN_RANGE_ATTACK1"
- " COND_CAN_RANGE_ATTACK2"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_BUGBAIT_DISTRACTION,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_RESET_ACTIVITY 0"
- " TASK_PLAY_SEQUENCE ACTIVITY:ACT_COMBINE_BUGBAIT"
- ""
- " Interrupts"
- ""
- )
-
- //=========================================================
- // SCHED_COMBINE_CHARGE_TURRET
- //
- // Used to run straight at enemy turrets to knock them over.
- // Prevents squadmates from throwing grenades during.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_CHARGE_TURRET,
-
- " Tasks"
- " TASK_COMBINE_DEFER_SQUAD_GRENADES 0"
- " TASK_STOP_MOVING 0"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
- " TASK_GET_CHASE_PATH_TO_ENEMY 300"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_FACE_ENEMY 0"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_ENEMY_UNREACHABLE"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- " COND_TOO_CLOSE_TO_ATTACK"
- " COND_TASK_FAILED"
- " COND_LOST_ENEMY"
- " COND_BETTER_WEAPON_AVAILABLE"
- " COND_HEAR_DANGER"
- )
-
- //=========================================================
- // SCHED_COMBINE_CHARGE_PLAYER
- //
- // Used to run straight at enemy player since physgun combat
- // is more fun when the enemies are close
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_CHARGE_PLAYER,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
- " TASK_COMBINE_CHASE_ENEMY_CONTINUOUSLY 192"
- " TASK_FACE_ENEMY 0"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_ENEMY_UNREACHABLE"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- " COND_TASK_FAILED"
- " COND_LOST_ENEMY"
- " COND_HEAR_DANGER"
- )
-
- //=========================================================
- // SCHED_COMBINE_DROP_GRENADE
- //
- // Place a grenade at my feet
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_DROP_GRENADE,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SPECIAL_ATTACK2"
- " TASK_FIND_COVER_FROM_ENEMY 99"
- " TASK_FIND_FAR_NODE_COVER_FROM_ENEMY 384"
- " TASK_CLEAR_MOVE_WAIT 0"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- ""
- " Interrupts"
- )
-
- //=========================================================
- // SCHED_COMBINE_PATROL_ENEMY
- //
- // Used instead if SCHED_COMBINE_PATROL if I have an enemy.
- // Wait for the enemy a bit in the hopes of ambushing him.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_PATROL_ENEMY,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_WAIT_FACE_ENEMY 1"
- " TASK_WAIT_FACE_ENEMY_RANDOM 3"
- ""
- " Interrupts"
- " COND_ENEMY_DEAD"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- " COND_HEAR_DANGER"
- " COND_HEAR_MOVE_AWAY"
- " COND_NEW_ENEMY"
- " COND_SEE_ENEMY"
- " COND_CAN_RANGE_ATTACK1"
- " COND_CAN_RANGE_ATTACK2"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_BURNING_STAND,
-
- " Tasks"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_COMBINE_BUGBAIT"
- " TASK_RANDOMIZE_FRAMERATE 20"
- " TASK_WAIT 2"
- " TASK_WAIT_RANDOM 3"
- " TASK_COMBINE_DIE_INSTANTLY DMG_BURN"
- " TASK_WAIT 1.0"
- " "
- " Interrupts"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_FACE_IDEAL_YAW,
-
- " Tasks"
- " TASK_FACE_IDEAL 0"
- " "
- " Interrupts"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_COMBINE_MOVE_TO_MELEE,
-
- " Tasks"
- " TASK_STORE_ENEMY_POSITION_IN_SAVEPOSITION 0"
- " TASK_GET_PATH_TO_SAVEPOSITION 0"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " "
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_CAN_MELEE_ATTACK1"
- )
-
- AI_END_CUSTOM_NPC()
+//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "ai_hull.h" +#include "ai_navigator.h" +#include "ai_motor.h" +#include "ai_squadslot.h" +#include "ai_squad.h" +#include "ai_route.h" +#include "ai_interactions.h" +#include "ai_tacticalservices.h" +#include "soundent.h" +#include "game.h" +#include "npcevent.h" +#include "npc_combine.h" +#include "activitylist.h" +#include "player.h" +#include "basecombatweapon.h" +#include "basegrenade_shared.h" +#include "vstdlib/random.h" +#include "engine/IEngineSound.h" +#include "globals.h" +#include "grenade_frag.h" +#include "ndebugoverlay.h" +#include "weapon_physcannon.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "npc_headcrab.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +int g_fCombineQuestion; // true if an idle grunt asked a question. Cleared when someone answers. YUCK old global from grunt code + +#define COMBINE_SKIN_DEFAULT 0 +#define COMBINE_SKIN_SHOTGUNNER 1 + + +#define COMBINE_GRENADE_THROW_SPEED 650 +#define COMBINE_GRENADE_TIMER 3.5 +#define COMBINE_GRENADE_FLUSH_TIME 3.0 // Don't try to flush an enemy who has been out of sight for longer than this. +#define COMBINE_GRENADE_FLUSH_DIST 256.0 // Don't try to flush an enemy who has moved farther than this distance from the last place I saw him. + +#define COMBINE_LIMP_HEALTH 20 +#define COMBINE_MIN_GRENADE_CLEAR_DIST 250 + +#define COMBINE_EYE_STANDING_POSITION Vector( 0, 0, 66 ) +#define COMBINE_GUN_STANDING_POSITION Vector( 0, 0, 57 ) +#define COMBINE_EYE_CROUCHING_POSITION Vector( 0, 0, 40 ) +#define COMBINE_GUN_CROUCHING_POSITION Vector( 0, 0, 36 ) +#define COMBINE_SHOTGUN_STANDING_POSITION Vector( 0, 0, 36 ) +#define COMBINE_SHOTGUN_CROUCHING_POSITION Vector( 0, 0, 36 ) +#define COMBINE_MIN_CROUCH_DISTANCE 256.0 + +//----------------------------------------------------------------------------- +// Static stuff local to this file. +//----------------------------------------------------------------------------- +// This is the index to the name of the shotgun's classname in the string pool +// so that we can get away with an integer compare rather than a string compare. +string_t s_iszShotgunClassname; + +//----------------------------------------------------------------------------- +// Interactions +//----------------------------------------------------------------------------- +int g_interactionCombineBash = 0; // melee bash attack + +//========================================================= +// Combines's Anim Events Go Here +//========================================================= +#define COMBINE_AE_RELOAD ( 2 ) +#define COMBINE_AE_KICK ( 3 ) +#define COMBINE_AE_AIM ( 4 ) +#define COMBINE_AE_GREN_TOSS ( 7 ) +#define COMBINE_AE_GREN_LAUNCH ( 8 ) +#define COMBINE_AE_GREN_DROP ( 9 ) +#define COMBINE_AE_CAUGHT_ENEMY ( 10) // grunt established sight with an enemy (player only) that had previously eluded the squad. + +int COMBINE_AE_BEGIN_ALTFIRE; +int COMBINE_AE_ALTFIRE; + +//========================================================= +// Combine activities +//========================================================= +//Activity ACT_COMBINE_STANDING_SMG1; +//Activity ACT_COMBINE_CROUCHING_SMG1; +//Activity ACT_COMBINE_STANDING_AR2; +//Activity ACT_COMBINE_CROUCHING_AR2; +//Activity ACT_COMBINE_WALKING_AR2; +//Activity ACT_COMBINE_STANDING_SHOTGUN; +//Activity ACT_COMBINE_CROUCHING_SHOTGUN; +Activity ACT_COMBINE_THROW_GRENADE; +Activity ACT_COMBINE_LAUNCH_GRENADE; +Activity ACT_COMBINE_BUGBAIT; +Activity ACT_COMBINE_AR2_ALTFIRE; +Activity ACT_WALK_EASY; +Activity ACT_WALK_MARCH; + +// ----------------------------------------------- +// > Squad slots +// ----------------------------------------------- +enum SquadSlot_T +{ + SQUAD_SLOT_GRENADE1 = LAST_SHARED_SQUADSLOT, + SQUAD_SLOT_GRENADE2, + SQUAD_SLOT_ATTACK_OCCLUDER, + SQUAD_SLOT_OVERWATCH, +}; + +enum TacticalVariant_T +{ + TACTICAL_VARIANT_DEFAULT = 0, + TACTICAL_VARIANT_PRESSURE_ENEMY, // Always try to close in on the player. + TACTICAL_VARIANT_PRESSURE_ENEMY_UNTIL_CLOSE, // Act like VARIANT_PRESSURE_ENEMY, but go to VARIANT_DEFAULT once within 30 feet +}; + +enum PathfindingVariant_T +{ + PATHFINDING_VARIANT_DEFAULT = 0, +}; + + +#define bits_MEMORY_PAIN_LIGHT_SOUND bits_MEMORY_CUSTOM1 +#define bits_MEMORY_PAIN_HEAVY_SOUND bits_MEMORY_CUSTOM2 +#define bits_MEMORY_PLAYER_HURT bits_MEMORY_CUSTOM3 + +LINK_ENTITY_TO_CLASS( npc_combine, CNPC_Combine ); + +//--------------------------------------------------------- +// Save/Restore +//--------------------------------------------------------- +BEGIN_DATADESC( CNPC_Combine ) + +DEFINE_FIELD( m_nKickDamage, FIELD_INTEGER ), +DEFINE_FIELD( m_vecTossVelocity, FIELD_VECTOR ), +DEFINE_FIELD( m_hForcedGrenadeTarget, FIELD_EHANDLE ), +DEFINE_FIELD( m_bShouldPatrol, FIELD_BOOLEAN ), +DEFINE_FIELD( m_bFirstEncounter, FIELD_BOOLEAN ), +DEFINE_FIELD( m_flNextPainSoundTime, FIELD_TIME ), +DEFINE_FIELD( m_flNextAlertSoundTime, FIELD_TIME ), +DEFINE_FIELD( m_flNextGrenadeCheck, FIELD_TIME ), +DEFINE_FIELD( m_flNextLostSoundTime, FIELD_TIME ), +DEFINE_FIELD( m_flAlertPatrolTime, FIELD_TIME ), +DEFINE_FIELD( m_flNextAltFireTime, FIELD_TIME ), +DEFINE_FIELD( m_nShots, FIELD_INTEGER ), +DEFINE_FIELD( m_flShotDelay, FIELD_FLOAT ), +DEFINE_FIELD( m_flStopMoveShootTime, FIELD_TIME ), +DEFINE_KEYFIELD( m_iNumGrenades, FIELD_INTEGER, "NumGrenades" ), +DEFINE_EMBEDDED( m_Sentences ), + +// m_AssaultBehavior (auto saved by AI) +// m_StandoffBehavior (auto saved by AI) +// m_FollowBehavior (auto saved by AI) +// m_FuncTankBehavior (auto saved by AI) +// m_RappelBehavior (auto saved by AI) +// m_ActBusyBehavior (auto saved by AI) + +DEFINE_INPUTFUNC( FIELD_VOID, "LookOff", InputLookOff ), +DEFINE_INPUTFUNC( FIELD_VOID, "LookOn", InputLookOn ), + +DEFINE_INPUTFUNC( FIELD_VOID, "StartPatrolling", InputStartPatrolling ), +DEFINE_INPUTFUNC( FIELD_VOID, "StopPatrolling", InputStopPatrolling ), + +DEFINE_INPUTFUNC( FIELD_STRING, "Assault", InputAssault ), + +DEFINE_INPUTFUNC( FIELD_VOID, "HitByBugbait", InputHitByBugbait ), + +DEFINE_INPUTFUNC( FIELD_STRING, "ThrowGrenadeAtTarget", InputThrowGrenadeAtTarget ), + +DEFINE_FIELD( m_iLastAnimEventHandled, FIELD_INTEGER ), +DEFINE_FIELD( m_fIsElite, FIELD_BOOLEAN ), +DEFINE_FIELD( m_vecAltFireTarget, FIELD_VECTOR ), + +DEFINE_KEYFIELD( m_iTacticalVariant, FIELD_INTEGER, "tacticalvariant" ), +DEFINE_KEYFIELD( m_iPathfindingVariant, FIELD_INTEGER, "pathfindingvariant" ), + +END_DATADESC() + + +//------------------------------------------------------------------------------ +// Constructor. +//------------------------------------------------------------------------------ +CNPC_Combine::CNPC_Combine() +{ + m_vecTossVelocity = vec3_origin; +} + + +//----------------------------------------------------------------------------- +// Create components +//----------------------------------------------------------------------------- +bool CNPC_Combine::CreateComponents() +{ + if ( !BaseClass::CreateComponents() ) + return false; + + m_Sentences.Init( this, "NPC_Combine.SentenceParameters" ); + return true; +} + + +//------------------------------------------------------------------------------ +// Purpose: Don't look, only get info from squad. +//------------------------------------------------------------------------------ +void CNPC_Combine::InputLookOff( inputdata_t &inputdata ) +{ + m_spawnflags |= SF_COMBINE_NO_LOOK; +} + +//------------------------------------------------------------------------------ +// Purpose: Enable looking. +//------------------------------------------------------------------------------ +void CNPC_Combine::InputLookOn( inputdata_t &inputdata ) +{ + m_spawnflags &= ~SF_COMBINE_NO_LOOK; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Combine::InputStartPatrolling( inputdata_t &inputdata ) +{ + m_bShouldPatrol = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Combine::InputStopPatrolling( inputdata_t &inputdata ) +{ + m_bShouldPatrol = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Combine::InputAssault( inputdata_t &inputdata ) +{ + m_AssaultBehavior.SetParameters( AllocPooledString(inputdata.value.String()), CUE_DONT_WAIT, RALLY_POINT_SELECT_DEFAULT ); +} + +//----------------------------------------------------------------------------- +// We were hit by bugbait +//----------------------------------------------------------------------------- +void CNPC_Combine::InputHitByBugbait( inputdata_t &inputdata ) +{ + SetCondition( COND_COMBINE_HIT_BY_BUGBAIT ); +} + +//----------------------------------------------------------------------------- +// Purpose: Force the combine soldier to throw a grenade at the target +// If I'm a combine elite, fire my combine ball at the target instead. +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CNPC_Combine::InputThrowGrenadeAtTarget( inputdata_t &inputdata ) +{ + // Ignore if we're inside a scripted sequence + if ( m_NPCState == NPC_STATE_SCRIPT && m_hCine ) + return; + + CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.String(), NULL, inputdata.pActivator, inputdata.pCaller ); + if ( !pEntity ) + { + DevMsg("%s (%s) received ThrowGrenadeAtTarget input, but couldn't find target entity '%s'\n", GetClassname(), GetDebugName(), inputdata.value.String() ); + return; + } + + m_hForcedGrenadeTarget = pEntity; + m_flNextGrenadeCheck = 0; + + ClearSchedule( "Told to throw grenade via input" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Combine::Precache() +{ + PrecacheModel("models/Weapons/w_grenade.mdl"); + UTIL_PrecacheOther( "npc_handgrenade" ); + + PrecacheScriptSound( "NPC_Combine.GrenadeLaunch" ); + PrecacheScriptSound( "NPC_Combine.WeaponBash" ); + PrecacheScriptSound( "Weapon_CombineGuard.Special1" ); + + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Combine::Activate() +{ + s_iszShotgunClassname = FindPooledString( "weapon_shotgun" ); + BaseClass::Activate(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// +// +//----------------------------------------------------------------------------- +void CNPC_Combine::Spawn( void ) +{ + SetHullType(HULL_HUMAN); + SetHullSizeNormal(); + + SetSolid( SOLID_BBOX ); + AddSolidFlags( FSOLID_NOT_STANDABLE ); + SetMoveType( MOVETYPE_STEP ); + SetBloodColor( BLOOD_COLOR_RED ); + m_flFieldOfView = -0.2;// indicates the width of this NPC's forward view cone ( as a dotproduct result ) + m_NPCState = NPC_STATE_NONE; + m_flNextGrenadeCheck = gpGlobals->curtime + 1; + m_flNextPainSoundTime = 0; + m_flNextAlertSoundTime = 0; + m_bShouldPatrol = false; + + // CapabilitiesAdd( bits_CAP_TURN_HEAD | bits_CAP_MOVE_GROUND | bits_CAP_MOVE_JUMP | bits_CAP_MOVE_CLIMB); + // JAY: Disabled jump for now - hard to compare to HL1 + CapabilitiesAdd( bits_CAP_TURN_HEAD | bits_CAP_MOVE_GROUND ); + + CapabilitiesAdd( bits_CAP_AIM_GUN ); + + // Innate range attack for grenade + // CapabilitiesAdd(bits_CAP_INNATE_RANGE_ATTACK2 ); + + // Innate range attack for kicking + CapabilitiesAdd(bits_CAP_INNATE_MELEE_ATTACK1 ); + + // Can be in a squad + CapabilitiesAdd( bits_CAP_SQUAD); + CapabilitiesAdd( bits_CAP_USE_WEAPONS ); + + CapabilitiesAdd( bits_CAP_DUCK ); // In reloading and cover + + CapabilitiesAdd( bits_CAP_NO_HIT_SQUADMATES ); + + m_bFirstEncounter = true;// this is true when the grunt spawns, because he hasn't encountered an enemy yet. + + m_HackedGunPos = Vector ( 0, 0, 55 ); + + m_flStopMoveShootTime = FLT_MAX; // Move and shoot defaults on. + m_MoveAndShootOverlay.SetInitialDelay( 0.75 ); // But with a bit of a delay. + + m_flNextLostSoundTime = 0; + m_flAlertPatrolTime = 0; + + m_flNextAltFireTime = gpGlobals->curtime; + + NPCInit(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CNPC_Combine::CreateBehaviors() +{ + AddBehavior( &m_RappelBehavior ); + AddBehavior( &m_ActBusyBehavior ); + AddBehavior( &m_AssaultBehavior ); + AddBehavior( &m_StandoffBehavior ); + AddBehavior( &m_FollowBehavior ); + AddBehavior( &m_FuncTankBehavior ); + + return BaseClass::CreateBehaviors(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Combine::PostNPCInit() +{ + if( IsElite() ) + { + // Give a warning if a Combine Soldier is equipped with anything other than + // an AR2. + if( !GetActiveWeapon() || !FClassnameIs( GetActiveWeapon(), "weapon_ar2" ) ) + { + DevWarning("**Combine Elite Soldier MUST be equipped with AR2\n"); + } + } + + BaseClass::PostNPCInit(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Combine::GatherConditions() +{ + BaseClass::GatherConditions(); + + ClearCondition( COND_COMBINE_ATTACK_SLOT_AVAILABLE ); + + if( GetState() == NPC_STATE_COMBAT ) + { + if( IsCurSchedule( SCHED_COMBINE_WAIT_IN_COVER, false ) ) + { + // Soldiers that are standing around doing nothing poll for attack slots so + // that they can respond quickly when one comes available. If they can + // occupy a vacant attack slot, they do so. This holds the slot until their + // schedule breaks and schedule selection runs again, essentially reserving this + // slot. If they do not select an attack schedule, then they'll release the slot. + if( OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) ) + { + SetCondition( COND_COMBINE_ATTACK_SLOT_AVAILABLE ); + } + } + + if( IsUsingTacticalVariant(TACTICAL_VARIANT_PRESSURE_ENEMY_UNTIL_CLOSE) ) + { + if( GetEnemy() != NULL && !HasCondition(COND_ENEMY_OCCLUDED) ) + { + // Now we're close to our enemy, stop using the tactical variant. + if( GetAbsOrigin().DistToSqr(GetEnemy()->GetAbsOrigin()) < Square(30.0f * 12.0f) ) + m_iTacticalVariant = TACTICAL_VARIANT_DEFAULT; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Combine::PrescheduleThink() +{ + BaseClass::PrescheduleThink(); + + // Speak any queued sentences + m_Sentences.UpdateSentenceQueue(); + + if ( IsOnFire() ) + { + SetCondition( COND_COMBINE_ON_FIRE ); + } + else + { + ClearCondition( COND_COMBINE_ON_FIRE ); + } + + extern ConVar ai_debug_shoot_positions; + if ( ai_debug_shoot_positions.GetBool() ) + NDebugOverlay::Cross3D( EyePosition(), 16, 0, 255, 0, false, 0.1 ); + + if( gpGlobals->curtime >= m_flStopMoveShootTime ) + { + // Time to stop move and shoot and start facing the way I'm running. + // This makes the combine look attentive when disengaging, but prevents + // them from always running around facing you. + // + // Only do this if it won't be immediately shut off again. + if( GetNavigator()->GetPathTimeToGoal() > 1.0f ) + { + m_MoveAndShootOverlay.SuspendMoveAndShoot( 5.0f ); + m_flStopMoveShootTime = FLT_MAX; + } + } + + if( m_flGroundSpeed > 0 && GetState() == NPC_STATE_COMBAT && m_MoveAndShootOverlay.IsSuspended() ) + { + // Return to move and shoot when near my goal so that I 'tuck into' the location facing my enemy. + if( GetNavigator()->GetPathTimeToGoal() <= 1.0f ) + { + m_MoveAndShootOverlay.SuspendMoveAndShoot( 0 ); + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Combine::DelayAltFireAttack( float flDelay ) +{ + float flNextAltFire = gpGlobals->curtime + flDelay; + + if( flNextAltFire > m_flNextAltFireTime ) + { + // Don't let this delay order preempt a previous request to wait longer. + m_flNextAltFireTime = flNextAltFire; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Combine::DelaySquadAltFireAttack( float flDelay ) +{ + // Make sure to delay my own alt-fire attack. + DelayAltFireAttack( flDelay ); + + AISquadIter_t iter; + CAI_BaseNPC *pSquadmate = m_pSquad ? m_pSquad->GetFirstMember( &iter ) : NULL; + while ( pSquadmate ) + { + CNPC_Combine *pCombine = dynamic_cast<CNPC_Combine*>(pSquadmate); + + if( pCombine && pCombine->IsElite() ) + { + pCombine->DelayAltFireAttack( flDelay ); + } + + pSquadmate = m_pSquad->GetNextMember( &iter ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: degrees to turn in 0.1 seconds +//----------------------------------------------------------------------------- +float CNPC_Combine::MaxYawSpeed( void ) +{ + switch( GetActivity() ) + { + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + return 45; + break; + case ACT_RUN: + case ACT_RUN_HURT: + return 15; + break; + case ACT_WALK: + case ACT_WALK_CROUCH: + return 25; + break; + case ACT_RANGE_ATTACK1: + case ACT_RANGE_ATTACK2: + case ACT_MELEE_ATTACK1: + case ACT_MELEE_ATTACK2: + return 35; + default: + return 35; + break; + } +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CNPC_Combine::ShouldMoveAndShoot() +{ + // Set this timer so that gpGlobals->curtime can't catch up to it. + // Essentially, we're saying that we're not going to interfere with + // what the AI wants to do with move and shoot. + // + // If any code below changes this timer, the code is saying + // "It's OK to move and shoot until gpGlobals->curtime == m_flStopMoveShootTime" + m_flStopMoveShootTime = FLT_MAX; + + if( IsCurSchedule( SCHED_COMBINE_HIDE_AND_RELOAD, false ) ) + m_flStopMoveShootTime = gpGlobals->curtime + random->RandomFloat( 0.4f, 0.6f ); + + if( IsCurSchedule( SCHED_TAKE_COVER_FROM_BEST_SOUND, false ) ) + return false; + + if( IsCurSchedule( SCHED_COMBINE_TAKE_COVER_FROM_BEST_SOUND, false ) ) + return false; + + if( IsCurSchedule( SCHED_COMBINE_RUN_AWAY_FROM_BEST_SOUND, false ) ) + return false; + + if( HasCondition( COND_NO_PRIMARY_AMMO, false ) ) + m_flStopMoveShootTime = gpGlobals->curtime + random->RandomFloat( 0.4f, 0.6f ); + + if( m_pSquad && IsCurSchedule( SCHED_COMBINE_TAKE_COVER1, false ) ) + m_flStopMoveShootTime = gpGlobals->curtime + random->RandomFloat( 0.4f, 0.6f ); + + return BaseClass::ShouldMoveAndShoot(); +} + +//----------------------------------------------------------------------------- +// Purpose: turn in the direction of movement +// Output : +//----------------------------------------------------------------------------- +bool CNPC_Combine::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval ) +{ + return BaseClass::OverrideMoveFacing( move, flInterval ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// +// +//----------------------------------------------------------------------------- +Class_T CNPC_Combine::Classify ( void ) +{ + return CLASS_COMBINE; +} + + +//----------------------------------------------------------------------------- +// Continuous movement tasks +//----------------------------------------------------------------------------- +bool CNPC_Combine::IsCurTaskContinuousMove() +{ + const Task_t* pTask = GetTask(); + if ( pTask && (pTask->iTask == TASK_COMBINE_CHASE_ENEMY_CONTINUOUSLY) ) + return true; + + return BaseClass::IsCurTaskContinuousMove(); +} + + +//----------------------------------------------------------------------------- +// Chase the enemy, updating the target position as the player moves +//----------------------------------------------------------------------------- +void CNPC_Combine::StartTaskChaseEnemyContinuously( const Task_t *pTask ) +{ + CBaseEntity *pEnemy = GetEnemy(); + if ( !pEnemy ) + { + TaskFail( FAIL_NO_ENEMY ); + return; + } + + // We're done once we get close enough + if ( WorldSpaceCenter().DistToSqr( pEnemy->WorldSpaceCenter() ) <= pTask->flTaskData * pTask->flTaskData ) + { + TaskComplete(); + return; + } + + // TASK_GET_PATH_TO_ENEMY + if ( IsUnreachable( pEnemy ) ) + { + TaskFail(FAIL_NO_ROUTE); + return; + } + + if ( !GetNavigator()->SetGoal( GOALTYPE_ENEMY, AIN_NO_PATH_TASK_FAIL ) ) + { + // no way to get there =( + DevWarning( 2, "GetPathToEnemy failed!!\n" ); + RememberUnreachable( pEnemy ); + TaskFail(FAIL_NO_ROUTE); + return; + } + + // NOTE: This is TaskRunPath here. + if ( TranslateActivity( ACT_RUN ) != ACT_INVALID ) + { + GetNavigator()->SetMovementActivity( ACT_RUN ); + } + else + { + GetNavigator()->SetMovementActivity(ACT_WALK); + } + + // Cover is void once I move + Forget( bits_MEMORY_INCOVER ); + + if (GetNavigator()->GetGoalType() == GOALTYPE_NONE) + { + TaskComplete(); + GetNavigator()->ClearGoal(); // Clear residual state + return; + } + + // No shooting delay when in this mode + m_MoveAndShootOverlay.SetInitialDelay( 0.0 ); + + if (!GetNavigator()->IsGoalActive()) + { + SetIdealActivity( GetStoppedActivity() ); + } + else + { + // Check validity of goal type + ValidateNavGoal(); + } + + // set that we're probably going to stop before the goal + GetNavigator()->SetArrivalDistance( pTask->flTaskData ); + m_vSavePosition = GetEnemy()->WorldSpaceCenter(); +} + +void CNPC_Combine::RunTaskChaseEnemyContinuously( const Task_t *pTask ) +{ + if (!GetNavigator()->IsGoalActive()) + { + SetIdealActivity( GetStoppedActivity() ); + } + else + { + // Check validity of goal type + ValidateNavGoal(); + } + + CBaseEntity *pEnemy = GetEnemy(); + if ( !pEnemy ) + { + TaskFail( FAIL_NO_ENEMY ); + return; + } + + // We're done once we get close enough + if ( WorldSpaceCenter().DistToSqr( pEnemy->WorldSpaceCenter() ) <= pTask->flTaskData * pTask->flTaskData ) + { + GetNavigator()->StopMoving(); + TaskComplete(); + return; + } + + // Recompute path if the enemy has moved too much + if ( m_vSavePosition.DistToSqr( pEnemy->WorldSpaceCenter() ) < (pTask->flTaskData * pTask->flTaskData) ) + return; + + if ( IsUnreachable( pEnemy ) ) + { + TaskFail(FAIL_NO_ROUTE); + return; + } + + if ( !GetNavigator()->RefindPathToGoal() ) + { + TaskFail(FAIL_NO_ROUTE); + return; + } + + m_vSavePosition = pEnemy->WorldSpaceCenter(); +} + + +//========================================================= +// start task +//========================================================= +void CNPC_Combine::StartTask( const Task_t *pTask ) +{ + // NOTE: This reset is required because we change it in TASK_COMBINE_CHASE_ENEMY_CONTINUOUSLY + m_MoveAndShootOverlay.SetInitialDelay( 0.75 ); + + switch ( pTask->iTask ) + { + case TASK_COMBINE_SET_STANDING: + { + if ( pTask->flTaskData == 1.0f) + { + Stand(); + } + else + { + Crouch(); + } + TaskComplete(); + } + break; + + case TASK_COMBINE_CHASE_ENEMY_CONTINUOUSLY: + StartTaskChaseEnemyContinuously( pTask ); + break; + + case TASK_COMBINE_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET: + SetIdealActivity( (Activity)(int)pTask->flTaskData ); + GetMotor()->SetIdealYawToTargetAndUpdate( m_vecAltFireTarget, AI_KEEP_YAW_SPEED ); + break; + + case TASK_COMBINE_SIGNAL_BEST_SOUND: + if( IsInSquad() && GetSquad()->NumMembers() > 1 ) + { + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + + if( pPlayer && OccupyStrategySlot( SQUAD_SLOT_EXCLUSIVE_HANDSIGN ) && pPlayer->FInViewCone( this ) ) + { + CSound *pSound; + pSound = GetBestSound(); + + Assert( pSound != NULL ); + + if ( pSound ) + { + Vector right, tosound; + + GetVectors( NULL, &right, NULL ); + + tosound = pSound->GetSoundReactOrigin() - GetAbsOrigin(); + VectorNormalize( tosound); + + tosound.z = 0; + right.z = 0; + + if( DotProduct( right, tosound ) > 0 ) + { + // Right + SetIdealActivity( ACT_SIGNAL_RIGHT ); + } + else + { + // Left + SetIdealActivity( ACT_SIGNAL_LEFT ); + } + + break; + } + } + } + + // Otherwise, just skip it. + TaskComplete(); + break; + + case TASK_ANNOUNCE_ATTACK: + { + // If Primary Attack + if ((int)pTask->flTaskData == 1) + { + // ----------------------------------------------------------- + // If enemy isn't facing me and I haven't attacked in a while + // annouce my attack before I start wailing away + // ----------------------------------------------------------- + CBaseCombatCharacter *pBCC = GetEnemyCombatCharacterPointer(); + + if (pBCC && pBCC->IsPlayer() && (!pBCC->FInViewCone ( this )) && + (gpGlobals->curtime - m_flLastAttackTime > 3.0) ) + { + m_flLastAttackTime = gpGlobals->curtime; + + m_Sentences.Speak( "COMBINE_ANNOUNCE", SENTENCE_PRIORITY_HIGH ); + + // Wait two seconds + SetWait( 2.0 ); + + if ( !IsCrouching() ) + { + SetActivity(ACT_IDLE); + } + else + { + SetActivity(ACT_COWER); // This is really crouch idle + } + } + // ------------------------------------------------------------- + // Otherwise move on + // ------------------------------------------------------------- + else + { + TaskComplete(); + } + } + else + { + m_Sentences.Speak( "COMBINE_THROW_GRENADE", SENTENCE_PRIORITY_MEDIUM ); + SetActivity(ACT_IDLE); + + // Wait two seconds + SetWait( 2.0 ); + } + break; + } + + case TASK_WALK_PATH: + case TASK_RUN_PATH: + // grunt no longer assumes he is covered if he moves + Forget( bits_MEMORY_INCOVER ); + BaseClass::StartTask( pTask ); + break; + + case TASK_COMBINE_FACE_TOSS_DIR: + break; + + case TASK_COMBINE_GET_PATH_TO_FORCED_GREN_LOS: + { + if ( !m_hForcedGrenadeTarget ) + { + TaskFail(FAIL_NO_ENEMY); + return; + } + + float flMaxRange = 2000; + float flMinRange = 0; + + Vector vecEnemy = m_hForcedGrenadeTarget->GetAbsOrigin(); + Vector vecEnemyEye = vecEnemy + m_hForcedGrenadeTarget->GetViewOffset(); + + Vector posLos; + bool found = false; + + if ( GetTacticalServices()->FindLateralLos( vecEnemyEye, &posLos ) ) + { + float dist = ( posLos - vecEnemyEye ).Length(); + if ( dist < flMaxRange && dist > flMinRange ) + found = true; + } + + if ( !found && GetTacticalServices()->FindLos( vecEnemy, vecEnemyEye, flMinRange, flMaxRange, 1.0, &posLos ) ) + { + found = true; + } + + if ( !found ) + { + TaskFail( FAIL_NO_SHOOT ); + } + else + { + // else drop into run task to offer an interrupt + m_vInterruptSavePosition = posLos; + } + } + break; + + case TASK_COMBINE_IGNORE_ATTACKS: + // must be in a squad + if (m_pSquad && m_pSquad->NumMembers() > 2) + { + // the enemy must be far enough away + if (GetEnemy() && (GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter()).Length() > 512.0 ) + { + m_flNextAttack = gpGlobals->curtime + pTask->flTaskData; + } + } + TaskComplete( ); + break; + + case TASK_COMBINE_DEFER_SQUAD_GRENADES: + { + if ( m_pSquad ) + { + // iterate my squad and stop everyone from throwing grenades for a little while. + AISquadIter_t iter; + + CAI_BaseNPC *pSquadmate = m_pSquad ? m_pSquad->GetFirstMember( &iter ) : NULL; + while ( pSquadmate ) + { + CNPC_Combine *pCombine = dynamic_cast<CNPC_Combine*>(pSquadmate); + + if( pCombine ) + { + pCombine->m_flNextGrenadeCheck = gpGlobals->curtime + 5; + } + + pSquadmate = m_pSquad->GetNextMember( &iter ); + } + } + + TaskComplete(); + break; + } + + case TASK_FACE_IDEAL: + case TASK_FACE_ENEMY: + { + if( pTask->iTask == TASK_FACE_ENEMY && HasCondition( COND_CAN_RANGE_ATTACK1 ) ) + { + TaskComplete(); + return; + } + + BaseClass::StartTask( pTask ); + bool bIsFlying = (GetMoveType() == MOVETYPE_FLY) || (GetMoveType() == MOVETYPE_FLYGRAVITY); + if (bIsFlying) + { + SetIdealActivity( ACT_GLIDE ); + } + + } + break; + + case TASK_FIND_COVER_FROM_ENEMY: + { + if (GetHintGroup() == NULL_STRING) + { + CBaseEntity *pEntity = GetEnemy(); + + // FIXME: this should be generalized by the schedules that are selected, or in the definition of + // what "cover" means (i.e., trace attack vulnerability vs. physical attack vulnerability + if ( pEntity ) + { + // NOTE: This is a good time to check to see if the player is hurt. + // Have the combine notice this and call out + if ( !HasMemory(bits_MEMORY_PLAYER_HURT) && pEntity->IsPlayer() && pEntity->GetHealth() <= 20 ) + { + if ( m_pSquad ) + { + m_pSquad->SquadRemember(bits_MEMORY_PLAYER_HURT); + } + + m_Sentences.Speak( "COMBINE_PLAYERHIT", SENTENCE_PRIORITY_INVALID ); + JustMadeSound( SENTENCE_PRIORITY_HIGH ); + } + if ( pEntity->MyNPCPointer() ) + { + if ( !(pEntity->MyNPCPointer()->CapabilitiesGet( ) & bits_CAP_WEAPON_RANGE_ATTACK1) && + !(pEntity->MyNPCPointer()->CapabilitiesGet( ) & bits_CAP_INNATE_RANGE_ATTACK1) ) + { + TaskComplete(); + return; + } + } + } + } + BaseClass::StartTask( pTask ); + } + break; + case TASK_RANGE_ATTACK1: + { + m_nShots = GetActiveWeapon()->GetRandomBurst(); + m_flShotDelay = GetActiveWeapon()->GetFireRate(); + + m_flNextAttack = gpGlobals->curtime + m_flShotDelay - 0.1; + ResetIdealActivity( ACT_RANGE_ATTACK1 ); + m_flLastAttackTime = gpGlobals->curtime; + } + break; + + case TASK_COMBINE_DIE_INSTANTLY: + { + CTakeDamageInfo info; + + info.SetAttacker( this ); + info.SetInflictor( this ); + info.SetDamage( m_iHealth ); + info.SetDamageType( pTask->flTaskData ); + info.SetDamageForce( Vector( 0.1, 0.1, 0.1 ) ); + + TakeDamage( info ); + + TaskComplete(); + } + break; + + default: + BaseClass:: StartTask( pTask ); + break; + } +} + +//========================================================= +// RunTask +//========================================================= +void CNPC_Combine::RunTask( const Task_t *pTask ) +{ + /* + { + CBaseEntity *pEnemy = GetEnemy(); + if (pEnemy) + { + NDebugOverlay::Line(Center(), pEnemy->Center(), 0,255,255, false, 0.1); + } + + } + */ + + /* + if (m_iMySquadSlot != SQUAD_SLOT_NONE) + { + char text[64]; + Q_snprintf( text, strlen( text ), "%d", m_iMySquadSlot ); + + NDebugOverlay::Text( Center() + Vector( 0, 0, 72 ), text, false, 0.1 ); + } + */ + + switch ( pTask->iTask ) + { + case TASK_COMBINE_CHASE_ENEMY_CONTINUOUSLY: + RunTaskChaseEnemyContinuously( pTask ); + break; + + case TASK_COMBINE_SIGNAL_BEST_SOUND: + AutoMovement( ); + if ( IsActivityFinished() ) + { + TaskComplete(); + } + break; + + case TASK_ANNOUNCE_ATTACK: + { + // Stop waiting if enemy facing me or lost enemy + CBaseCombatCharacter* pBCC = GetEnemyCombatCharacterPointer(); + if (!pBCC || pBCC->FInViewCone( this )) + { + TaskComplete(); + } + + if ( IsWaitFinished() ) + { + TaskComplete(); + } + } + break; + + case TASK_COMBINE_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET: + GetMotor()->SetIdealYawToTargetAndUpdate( m_vecAltFireTarget, AI_KEEP_YAW_SPEED ); + + if ( IsActivityFinished() ) + { + TaskComplete(); + } + break; + + case TASK_COMBINE_FACE_TOSS_DIR: + { + // project a point along the toss vector and turn to face that point. + GetMotor()->SetIdealYawToTargetAndUpdate( GetLocalOrigin() + m_vecTossVelocity * 64, AI_KEEP_YAW_SPEED ); + + if ( FacingIdeal() ) + { + TaskComplete( true ); + } + break; + } + + case TASK_COMBINE_GET_PATH_TO_FORCED_GREN_LOS: + { + if ( !m_hForcedGrenadeTarget ) + { + TaskFail(FAIL_NO_ENEMY); + return; + } + + if ( GetTaskInterrupt() > 0 ) + { + ClearTaskInterrupt(); + + Vector vecEnemy = m_hForcedGrenadeTarget->GetAbsOrigin(); + AI_NavGoal_t goal( m_vInterruptSavePosition, ACT_RUN, AIN_HULL_TOLERANCE ); + + GetNavigator()->SetGoal( goal, AIN_CLEAR_TARGET ); + GetNavigator()->SetArrivalDirection( vecEnemy - goal.dest ); + } + else + { + TaskInterrupt(); + } + } + break; + + case TASK_RANGE_ATTACK1: + { + AutoMovement( ); + + Vector vecEnemyLKP = GetEnemyLKP(); + if (!FInAimCone( vecEnemyLKP )) + { + GetMotor()->SetIdealYawToTargetAndUpdate( vecEnemyLKP, AI_KEEP_YAW_SPEED ); + } + else + { + GetMotor()->SetIdealYawAndUpdate( GetMotor()->GetIdealYaw(), AI_KEEP_YAW_SPEED ); + } + + if ( gpGlobals->curtime >= m_flNextAttack ) + { + if ( IsActivityFinished() ) + { + if (--m_nShots > 0) + { + // DevMsg("ACT_RANGE_ATTACK1\n"); + ResetIdealActivity( ACT_RANGE_ATTACK1 ); + m_flLastAttackTime = gpGlobals->curtime; + m_flNextAttack = gpGlobals->curtime + m_flShotDelay - 0.1; + } + else + { + // DevMsg("TASK_RANGE_ATTACK1 complete\n"); + TaskComplete(); + } + } + } + else + { + // DevMsg("Wait\n"); + } + } + break; + + default: + { + BaseClass::RunTask( pTask ); + break; + } + } +} + +//------------------------------------------------------------------------------ +// Purpose : Override to always shoot at eyes (for ducking behind things) +// Input : +// Output : +//------------------------------------------------------------------------------ +Vector CNPC_Combine::BodyTarget( const Vector &posSrc, bool bNoisy ) +{ + Vector result = BaseClass::BodyTarget( posSrc, bNoisy ); + + // @TODO (toml 02-02-04): this seems wrong. Isn't this already be accounted for + // with the eye position used in the base BodyTarget() + if ( GetFlags() & FL_DUCKING ) + result -= Vector(0,0,24); + + return result; +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +bool CNPC_Combine::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker ) +{ + if( m_spawnflags & SF_COMBINE_NO_LOOK ) + { + // When no look is set, if enemy has eluded the squad, + // he's always invisble to me + if (GetEnemies()->HasEludedMe(pEntity)) + { + return false; + } + } + return BaseClass::FVisible(pEntity, traceMask, ppBlocker); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Combine::Event_Killed( const CTakeDamageInfo &info ) +{ + // if I was killed before I could finish throwing my grenade, drop + // a grenade item that the player can retrieve. + if( GetActivity() == ACT_RANGE_ATTACK2 ) + { + if( m_iLastAnimEventHandled != COMBINE_AE_GREN_TOSS ) + { + // Drop the grenade as an item. + Vector vecStart; + GetAttachment( "lefthand", vecStart ); + + CBaseEntity *pItem = DropItem( "weapon_frag", vecStart, RandomAngle(0,360) ); + + if ( pItem ) + { + IPhysicsObject *pObj = pItem->VPhysicsGetObject(); + + if ( pObj ) + { + Vector vel; + vel.x = random->RandomFloat( -100.0f, 100.0f ); + vel.y = random->RandomFloat( -100.0f, 100.0f ); + vel.z = random->RandomFloat( 800.0f, 1200.0f ); + AngularImpulse angImp = RandomAngularImpulse( -300.0f, 300.0f ); + + vel[2] = 0.0f; + pObj->AddVelocity( &vel, &angImp ); + } + + // In the Citadel we need to dissolve this + if ( PlayerHasMegaPhysCannon() ) + { + CBaseCombatWeapon *pWeapon = static_cast<CBaseCombatWeapon *>(pItem); + + pWeapon->Dissolve( NULL, gpGlobals->curtime, false, ENTITY_DISSOLVE_NORMAL ); + } + } + } + } + + BaseClass::Event_Killed( info ); +} + +//----------------------------------------------------------------------------- +// Purpose: Override. Don't update if I'm not looking +// Input : +// Output : Returns true is new enemy, false is known enemy +//----------------------------------------------------------------------------- +bool CNPC_Combine::UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer ) +{ + if( m_spawnflags & SF_COMBINE_NO_LOOK ) + { + return false; + } + + return BaseClass::UpdateEnemyMemory( pEnemy, position, pInformer ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Allows for modification of the interrupt mask for the current schedule. +// In the most cases the base implementation should be called first. +//----------------------------------------------------------------------------- +void CNPC_Combine::BuildScheduleTestBits( void ) +{ + BaseClass::BuildScheduleTestBits(); + + if (gpGlobals->curtime < m_flNextAttack) + { + ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 ); + ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 ); + } + + SetCustomInterruptCondition( COND_COMBINE_HIT_BY_BUGBAIT ); + + if ( !IsCurSchedule( SCHED_COMBINE_BURNING_STAND ) ) + { + SetCustomInterruptCondition( COND_COMBINE_ON_FIRE ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Translate base class activities into combot activites +//----------------------------------------------------------------------------- +Activity CNPC_Combine::NPC_TranslateActivity( Activity eNewActivity ) +{ + //Slaming this back to ACT_COMBINE_BUGBAIT since we don't want ANYTHING to change our activity while we burn. + if ( HasCondition( COND_COMBINE_ON_FIRE ) ) + return BaseClass::NPC_TranslateActivity( ACT_COMBINE_BUGBAIT ); + + if (eNewActivity == ACT_RANGE_ATTACK2) + { + // grunt is going to a secondary long range attack. This may be a thrown + // grenade or fired grenade, we must determine which and pick proper sequence + if (Weapon_OwnsThisType( "weapon_grenadelauncher" ) ) + { + return ( Activity )ACT_COMBINE_LAUNCH_GRENADE; + } + else + { + return ( Activity )ACT_COMBINE_THROW_GRENADE; + } + } + else if (eNewActivity == ACT_IDLE) + { + if ( !IsCrouching() && ( m_NPCState == NPC_STATE_COMBAT || m_NPCState == NPC_STATE_ALERT ) ) + { + eNewActivity = ACT_IDLE_ANGRY; + } + } + + if ( m_AssaultBehavior.IsRunning() ) + { + switch ( eNewActivity ) + { + case ACT_IDLE: + eNewActivity = ACT_IDLE_ANGRY; + break; + + case ACT_WALK: + eNewActivity = ACT_WALK_AIM; + break; + + case ACT_RUN: + eNewActivity = ACT_RUN_AIM; + break; + } + } + + return BaseClass::NPC_TranslateActivity( eNewActivity ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Overidden for human grunts because they hear the DANGER sound +// Input : +// Output : +//----------------------------------------------------------------------------- +int CNPC_Combine::GetSoundInterests( void ) +{ + return SOUND_WORLD | SOUND_COMBAT | SOUND_PLAYER | SOUND_DANGER | SOUND_PHYSICS_DANGER | SOUND_BULLET_IMPACT | SOUND_MOVE_AWAY; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this NPC can hear the specified sound +//----------------------------------------------------------------------------- +bool CNPC_Combine::QueryHearSound( CSound *pSound ) +{ + if ( pSound->SoundContext() & SOUND_CONTEXT_COMBINE_ONLY ) + return true; + + if ( pSound->SoundContext() & SOUND_CONTEXT_EXCLUDE_COMBINE ) + return false; + + return BaseClass::QueryHearSound( pSound ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Announce an assault if the enemy can see me and we are pretty +// close to him/her +// Input : +// Output : +//----------------------------------------------------------------------------- +void CNPC_Combine::AnnounceAssault(void) +{ + if (random->RandomInt(0,5) > 1) + return; + + // If enemy can see me make assualt sound + CBaseCombatCharacter* pBCC = GetEnemyCombatCharacterPointer(); + + if (!pBCC) + return; + + if (!FOkToMakeSound()) + return; + + // Make sure we are pretty close + if ( WorldSpaceCenter().DistToSqr( pBCC->WorldSpaceCenter() ) > (2000 * 2000)) + return; + + // Make sure we are in view cone of player + if (!pBCC->FInViewCone ( this )) + return; + + // Make sure player can see me + if ( FVisible( pBCC ) ) + { + m_Sentences.Speak( "COMBINE_ASSAULT" ); + } +} + + +void CNPC_Combine::AnnounceEnemyType( CBaseEntity *pEnemy ) +{ + const char *pSentenceName = "COMBINE_MONST"; + switch ( pEnemy->Classify() ) + { + case CLASS_PLAYER: + pSentenceName = "COMBINE_ALERT"; + break; + + case CLASS_PLAYER_ALLY: + case CLASS_CITIZEN_REBEL: + case CLASS_CITIZEN_PASSIVE: + case CLASS_VORTIGAUNT: + pSentenceName = "COMBINE_MONST_CITIZENS"; + break; + + case CLASS_PLAYER_ALLY_VITAL: + pSentenceName = "COMBINE_MONST_CHARACTER"; + break; + + case CLASS_ANTLION: + pSentenceName = "COMBINE_MONST_BUGS"; + break; + + case CLASS_ZOMBIE: + pSentenceName = "COMBINE_MONST_ZOMBIES"; + break; + + case CLASS_HEADCRAB: + case CLASS_BARNACLE: + pSentenceName = "COMBINE_MONST_PARASITES"; + break; + } + + m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_HIGH ); +} + +void CNPC_Combine::AnnounceEnemyKill( CBaseEntity *pEnemy ) +{ + if (!pEnemy ) + return; + + const char *pSentenceName = "COMBINE_KILL_MONST"; + switch ( pEnemy->Classify() ) + { + case CLASS_PLAYER: + pSentenceName = "COMBINE_PLAYER_DEAD"; + break; + + // no sentences for these guys yet + case CLASS_PLAYER_ALLY: + case CLASS_CITIZEN_REBEL: + case CLASS_CITIZEN_PASSIVE: + case CLASS_VORTIGAUNT: + break; + + case CLASS_PLAYER_ALLY_VITAL: + break; + + case CLASS_ANTLION: + break; + + case CLASS_ZOMBIE: + break; + + case CLASS_HEADCRAB: + case CLASS_BARNACLE: + break; + } + + m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_HIGH ); +} + +//----------------------------------------------------------------------------- +// Select the combat schedule +//----------------------------------------------------------------------------- +int CNPC_Combine::SelectCombatSchedule() +{ + // ----------- + // dead enemy + // ----------- + if ( HasCondition( COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return SCHED_NONE; + } + + // ----------- + // new enemy + // ----------- + if ( HasCondition( COND_NEW_ENEMY ) ) + { + CBaseEntity *pEnemy = GetEnemy(); + bool bFirstContact = false; + float flTimeSinceFirstSeen = gpGlobals->curtime - GetEnemies()->FirstTimeSeen( pEnemy ); + + if( flTimeSinceFirstSeen < 3.0f ) + bFirstContact = true; + + if ( m_pSquad && pEnemy ) + { + if ( HasCondition( COND_SEE_ENEMY ) ) + { + AnnounceEnemyType( pEnemy ); + } + + if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) && OccupyStrategySlot( SQUAD_SLOT_ATTACK1 ) ) + { + // Start suppressing if someone isn't firing already (SLOT_ATTACK1). This means + // I'm the guy who spotted the enemy, I should react immediately. + return SCHED_COMBINE_SUPPRESS; + } + + if ( m_pSquad->IsLeader( this ) || ( m_pSquad->GetLeader() && m_pSquad->GetLeader()->GetEnemy() != pEnemy ) ) + { + // I'm the leader, but I didn't get the job suppressing the enemy. We know this because + // This code only runs if the code above didn't assign me SCHED_COMBINE_SUPPRESS. + if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) && OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) ) + { + return SCHED_RANGE_ATTACK1; + } + + if( HasCondition(COND_WEAPON_HAS_LOS) && IsStrategySlotRangeOccupied( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) ) + { + // If everyone else is attacking and I have line of fire, wait for a chance to cover someone. + if( OccupyStrategySlot( SQUAD_SLOT_OVERWATCH ) ) + { + return SCHED_COMBINE_ENTER_OVERWATCH; + } + } + } + else + { + if ( m_pSquad->GetLeader() && FOkToMakeSound( SENTENCE_PRIORITY_MEDIUM ) ) + { + JustMadeSound( SENTENCE_PRIORITY_MEDIUM ); // squelch anything that isn't high priority so the leader can speak + } + + // First contact, and I'm solo, or not the squad leader. + if( HasCondition( COND_SEE_ENEMY ) && CanGrenadeEnemy() ) + { + if( OccupyStrategySlot( SQUAD_SLOT_GRENADE1 ) ) + { + return SCHED_RANGE_ATTACK2; + } + } + + if( !bFirstContact && OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) ) + { + if( random->RandomInt(0, 100) < 60 ) + { + return SCHED_ESTABLISH_LINE_OF_FIRE; + } + else + { + return SCHED_COMBINE_PRESS_ATTACK; + } + } + + return SCHED_TAKE_COVER_FROM_ENEMY; + } + } + } + + // --------------------- + // no ammo + // --------------------- + if ( ( HasCondition ( COND_NO_PRIMARY_AMMO ) || HasCondition ( COND_LOW_PRIMARY_AMMO ) ) && !HasCondition( COND_CAN_MELEE_ATTACK1) ) + { + return SCHED_HIDE_AND_RELOAD; + } + + // ---------------------- + // LIGHT DAMAGE + // ---------------------- + if ( HasCondition( COND_LIGHT_DAMAGE ) ) + { + if ( GetEnemy() != NULL ) + { + // only try to take cover if we actually have an enemy! + + // FIXME: need to take cover for enemy dealing the damage + + // A standing guy will either crouch or run. + // A crouching guy tries to stay stuck in. + if( !IsCrouching() ) + { + if( GetEnemy() && random->RandomFloat( 0, 100 ) < 50 && CouldShootIfCrouching( GetEnemy() ) ) + { + Crouch(); + } + else + { + //!!!KELLY - this grunt was hit and is going to run to cover. + // m_Sentences.Speak( "COMBINE_COVER" ); + return SCHED_TAKE_COVER_FROM_ENEMY; + } + } + } + else + { + // How am I wounded in combat with no enemy? + Assert( GetEnemy() != NULL ); + } + } + + // If I'm scared of this enemy run away + if ( IRelationType( GetEnemy() ) == D_FR ) + { + if (HasCondition( COND_SEE_ENEMY ) || + HasCondition( COND_SEE_FEAR ) || + HasCondition( COND_LIGHT_DAMAGE ) || + HasCondition( COND_HEAVY_DAMAGE )) + { + FearSound(); + //ClearCommandGoal(); + return SCHED_RUN_FROM_ENEMY; + } + + // If I've seen the enemy recently, cower. Ignore the time for unforgettable enemies. + AI_EnemyInfo_t *pMemory = GetEnemies()->Find( GetEnemy() ); + if ( (pMemory && pMemory->bUnforgettable) || (GetEnemyLastTimeSeen() > (gpGlobals->curtime - 5.0)) ) + { + // If we're facing him, just look ready. Otherwise, face him. + if ( FInAimCone( GetEnemy()->EyePosition() ) ) + return SCHED_COMBAT_STAND; + + return SCHED_FEAR_FACE; + } + } + + int attackSchedule = SelectScheduleAttack(); + if ( attackSchedule != SCHED_NONE ) + return attackSchedule; + + if (HasCondition(COND_ENEMY_OCCLUDED)) + { + // stand up, just in case + Stand(); + DesireStand(); + + if( GetEnemy() && !(GetEnemy()->GetFlags() & FL_NOTARGET) && OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) ) + { + // Charge in and break the enemy's cover! + return SCHED_ESTABLISH_LINE_OF_FIRE; + } + + // If I'm a long, long way away, establish a LOF anyway. Once I get there I'll + // start respecting the squad slots again. + float flDistSq = GetEnemy()->WorldSpaceCenter().DistToSqr( WorldSpaceCenter() ); + if ( flDistSq > Square(3000) ) + return SCHED_ESTABLISH_LINE_OF_FIRE; + + // Otherwise tuck in. + Remember( bits_MEMORY_INCOVER ); + return SCHED_COMBINE_WAIT_IN_COVER; + } + + // -------------------------------------------------------------- + // Enemy not occluded but isn't open to attack + // -------------------------------------------------------------- + if ( HasCondition( COND_SEE_ENEMY ) && !HasCondition( COND_CAN_RANGE_ATTACK1 ) ) + { + if ( (HasCondition( COND_TOO_FAR_TO_ATTACK ) || IsUsingTacticalVariant(TACTICAL_VARIANT_PRESSURE_ENEMY) ) && OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 )) + { + return SCHED_COMBINE_PRESS_ATTACK; + } + + AnnounceAssault(); + return SCHED_COMBINE_ASSAULT; + } + + return SCHED_NONE; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +int CNPC_Combine::SelectSchedule( void ) +{ + if ( IsWaitingToRappel() && BehaviorSelectSchedule() ) + { + return BaseClass::SelectSchedule(); + } + + if ( HasCondition(COND_COMBINE_ON_FIRE) ) + return SCHED_COMBINE_BURNING_STAND; + + int nSched = SelectFlinchSchedule(); + if ( nSched != SCHED_NONE ) + return nSched; + + if ( m_hForcedGrenadeTarget ) + { + if ( m_flNextGrenadeCheck < gpGlobals->curtime ) + { + Vector vecTarget = m_hForcedGrenadeTarget->WorldSpaceCenter(); + + if ( IsElite() ) + { + if ( FVisible( m_hForcedGrenadeTarget ) ) + { + m_vecAltFireTarget = vecTarget; + m_hForcedGrenadeTarget = NULL; + return SCHED_COMBINE_AR2_ALTFIRE; + } + } + else + { + // If we can, throw a grenade at the target. + // Ignore grenade count / distance / etc + if ( CheckCanThrowGrenade( vecTarget ) ) + { + m_hForcedGrenadeTarget = NULL; + return SCHED_COMBINE_FORCED_GRENADE_THROW; + } + } + } + + // Can't throw at the target, so lets try moving to somewhere where I can see it + if ( !FVisible( m_hForcedGrenadeTarget ) ) + { + return SCHED_COMBINE_MOVE_TO_FORCED_GREN_LOS; + } + } + + if ( m_NPCState != NPC_STATE_SCRIPT) + { + // If we're hit by bugbait, thrash around + if ( HasCondition( COND_COMBINE_HIT_BY_BUGBAIT ) ) + { + // Don't do this if we're mounting a func_tank + if ( m_FuncTankBehavior.IsMounted() == true ) + { + m_FuncTankBehavior.Dismount(); + } + + ClearCondition( COND_COMBINE_HIT_BY_BUGBAIT ); + return SCHED_COMBINE_BUGBAIT_DISTRACTION; + } + + // We've been told to move away from a target to make room for a grenade to be thrown at it + if ( HasCondition( COND_HEAR_MOVE_AWAY ) ) + { + return SCHED_MOVE_AWAY; + } + + // These things are done in any state but dead and prone + if (m_NPCState != NPC_STATE_DEAD && m_NPCState != NPC_STATE_PRONE ) + { + // Cower when physics objects are thrown at me + if ( HasCondition( COND_HEAR_PHYSICS_DANGER ) ) + { + return SCHED_FLINCH_PHYSICS; + } + + // grunts place HIGH priority on running away from danger sounds. + if ( HasCondition(COND_HEAR_DANGER) ) + { + CSound *pSound; + pSound = GetBestSound(); + + Assert( pSound != NULL ); + if ( pSound) + { + if (pSound->m_iType & SOUND_DANGER) + { + // I hear something dangerous, probably need to take cover. + // dangerous sound nearby!, call it out + const char *pSentenceName = "COMBINE_DANGER"; + + CBaseEntity *pSoundOwner = pSound->m_hOwner; + if ( pSoundOwner ) + { + CBaseGrenade *pGrenade = dynamic_cast<CBaseGrenade *>(pSoundOwner); + if ( pGrenade && pGrenade->GetThrower() ) + { + if ( IRelationType( pGrenade->GetThrower() ) != D_LI ) + { + // special case call out for enemy grenades + pSentenceName = "COMBINE_GREN"; + } + } + } + + m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_NORMAL, SENTENCE_CRITERIA_NORMAL ); + + // If the sound is approaching danger, I have no enemy, and I don't see it, turn to face. + if( !GetEnemy() && pSound->IsSoundType(SOUND_CONTEXT_DANGER_APPROACH) && pSound->m_hOwner && !FInViewCone(pSound->GetSoundReactOrigin()) ) + { + GetMotor()->SetIdealYawToTarget( pSound->GetSoundReactOrigin() ); + return SCHED_COMBINE_FACE_IDEAL_YAW; + } + + return SCHED_TAKE_COVER_FROM_BEST_SOUND; + } + + // JAY: This was disabled in HL1. Test? + if (!HasCondition( COND_SEE_ENEMY ) && ( pSound->m_iType & (SOUND_PLAYER | SOUND_COMBAT) )) + { + GetMotor()->SetIdealYawToTarget( pSound->GetSoundReactOrigin() ); + } + } + } + } + + if( BehaviorSelectSchedule() ) + { + return BaseClass::SelectSchedule(); + } + } + + switch ( m_NPCState ) + { + case NPC_STATE_IDLE: + { + if ( m_bShouldPatrol ) + return SCHED_COMBINE_PATROL; + } + // NOTE: Fall through! + + case NPC_STATE_ALERT: + { + if( HasCondition(COND_LIGHT_DAMAGE) || HasCondition(COND_HEAVY_DAMAGE) ) + { + AI_EnemyInfo_t *pDanger = GetEnemies()->GetDangerMemory(); + if( pDanger && FInViewCone(pDanger->vLastKnownLocation) && !BaseClass::FVisible(pDanger->vLastKnownLocation) ) + { + // I've been hurt, I'm facing the danger, but I don't see it, so move from this position. + return SCHED_TAKE_COVER_FROM_ORIGIN; + } + } + + if( HasCondition( COND_HEAR_COMBAT ) ) + { + CSound *pSound = GetBestSound(); + + if( pSound && pSound->IsSoundType( SOUND_COMBAT ) ) + { + if( m_pSquad && m_pSquad->GetSquadMemberNearestTo( pSound->GetSoundReactOrigin() ) == this && OccupyStrategySlot( SQUAD_SLOT_INVESTIGATE_SOUND ) ) + { + return SCHED_INVESTIGATE_SOUND; + } + } + } + + // Don't patrol if I'm in the middle of an assault, because I'll never return to the assault. + if ( !m_AssaultBehavior.HasAssaultCue() ) + { + if( m_bShouldPatrol || HasCondition( COND_COMBINE_SHOULD_PATROL ) ) + return SCHED_COMBINE_PATROL; + } + } + break; + + case NPC_STATE_COMBAT: + { + int nSched = SelectCombatSchedule(); + if ( nSched != SCHED_NONE ) + return nSched; + } + break; + } + + // no special cases here, call the base class + return BaseClass::SelectSchedule(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CNPC_Combine::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode ) +{ + if( failedSchedule == SCHED_COMBINE_TAKE_COVER1 ) + { + if( IsInSquad() && IsStrategySlotRangeOccupied(SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2) && HasCondition(COND_SEE_ENEMY) ) + { + // This eases the effects of an unfortunate bug that usually plagues shotgunners. Since their rate of fire is low, + // they spend relatively long periods of time without an attack squad slot. If you corner a shotgunner, usually + // the other memebers of the squad will hog all of the attack slots and pick schedules to move to establish line of + // fire. During this time, the shotgunner is prevented from attacking. If he also cannot find cover (the fallback case) + // he will stand around like an idiot, right in front of you. Instead of this, we have him run up to you for a melee attack. + return SCHED_COMBINE_MOVE_TO_MELEE; + } + } + + return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode ); +} + +//----------------------------------------------------------------------------- +// Should we charge the player? +//----------------------------------------------------------------------------- +bool CNPC_Combine::ShouldChargePlayer() +{ + return GetEnemy() && GetEnemy()->IsPlayer() && PlayerHasMegaPhysCannon() && !IsLimitingHintGroups(); +} + + +//----------------------------------------------------------------------------- +// Select attack schedules +//----------------------------------------------------------------------------- +#define COMBINE_MEGA_PHYSCANNON_ATTACK_DISTANCE 192 +#define COMBINE_MEGA_PHYSCANNON_ATTACK_DISTANCE_SQ (COMBINE_MEGA_PHYSCANNON_ATTACK_DISTANCE*COMBINE_MEGA_PHYSCANNON_ATTACK_DISTANCE) + +int CNPC_Combine::SelectScheduleAttack() +{ + // Drop a grenade? + if ( HasCondition( COND_COMBINE_DROP_GRENADE ) ) + return SCHED_COMBINE_DROP_GRENADE; + + // Kick attack? + if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) ) + { + return SCHED_MELEE_ATTACK1; + } + + // If I'm fighting a combine turret (it's been hacked to attack me), I can't really + // hurt it with bullets, so become grenade happy. + if ( GetEnemy() && GetEnemy()->Classify() == CLASS_COMBINE && FClassnameIs(GetEnemy(), "npc_turret_floor") ) + { + // Don't do this until I've been fighting the turret for a few seconds + float flTimeAtFirstHand = GetEnemies()->TimeAtFirstHand(GetEnemy()); + if ( flTimeAtFirstHand != AI_INVALID_TIME ) + { + float flTimeEnemySeen = gpGlobals->curtime - flTimeAtFirstHand; + if ( flTimeEnemySeen > 4.0 ) + { + if ( CanGrenadeEnemy() && OccupyStrategySlot( SQUAD_SLOT_GRENADE1 ) ) + return SCHED_RANGE_ATTACK2; + } + } + + // If we're not in the viewcone of the turret, run up and hit it. Do this a bit later to + // give other squadmembers a chance to throw a grenade before I run in. + if ( !GetEnemy()->MyNPCPointer()->FInViewCone( this ) && OccupyStrategySlot( SQUAD_SLOT_GRENADE1 ) ) + return SCHED_COMBINE_CHARGE_TURRET; + } + + // When fighting against the player who's wielding a mega-physcannon, + // always close the distance if possible + // But don't do it if you're in a nav-limited hint group + if ( ShouldChargePlayer() ) + { + float flDistSq = GetEnemy()->WorldSpaceCenter().DistToSqr( WorldSpaceCenter() ); + if ( flDistSq <= COMBINE_MEGA_PHYSCANNON_ATTACK_DISTANCE_SQ ) + { + if( HasCondition(COND_SEE_ENEMY) ) + { + if ( OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) ) + return SCHED_RANGE_ATTACK1; + } + else + { + if ( OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) ) + return SCHED_COMBINE_PRESS_ATTACK; + } + } + + if ( HasCondition(COND_SEE_ENEMY) && !IsUnreachable( GetEnemy() ) ) + { + return SCHED_COMBINE_CHARGE_PLAYER; + } + } + + // Can I shoot? + if ( HasCondition(COND_CAN_RANGE_ATTACK1) ) + { + + // JAY: HL1 behavior missing? +#if 0 + if ( m_pSquad ) + { + // if the enemy has eluded the squad and a squad member has just located the enemy + // and the enemy does not see the squad member, issue a call to the squad to waste a + // little time and give the player a chance to turn. + if ( MySquadLeader()->m_fEnemyEluded && !HasConditions ( bits_COND_ENEMY_FACING_ME ) ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + return SCHED_GRUNT_FOUND_ENEMY; + } + } +#endif + + // Engage if allowed + if ( OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) ) + { + return SCHED_RANGE_ATTACK1; + } + + // Throw a grenade if not allowed to engage with weapon. + if ( CanGrenadeEnemy() ) + { + if ( OccupyStrategySlot( SQUAD_SLOT_GRENADE1 ) ) + { + return SCHED_RANGE_ATTACK2; + } + } + + DesireCrouch(); + return SCHED_TAKE_COVER_FROM_ENEMY; + } + + if ( GetEnemy() && !HasCondition(COND_SEE_ENEMY) ) + { + // We don't see our enemy. If it hasn't been long since I last saw him, + // and he's pretty close to the last place I saw him, throw a grenade in + // to flush him out. A wee bit of cheating here... + + float flTime; + float flDist; + + flTime = gpGlobals->curtime - GetEnemies()->LastTimeSeen( GetEnemy() ); + flDist = ( GetEnemy()->GetAbsOrigin() - GetEnemies()->LastSeenPosition( GetEnemy() ) ).Length(); + + //Msg("Time: %f Dist: %f\n", flTime, flDist ); + if ( flTime <= COMBINE_GRENADE_FLUSH_TIME && flDist <= COMBINE_GRENADE_FLUSH_DIST && CanGrenadeEnemy( false ) && OccupyStrategySlot( SQUAD_SLOT_GRENADE1 ) ) + { + return SCHED_RANGE_ATTACK2; + } + } + + if (HasCondition(COND_WEAPON_SIGHT_OCCLUDED)) + { + // If they are hiding behind something that we can destroy, start shooting at it. + CBaseEntity *pBlocker = GetEnemyOccluder(); + if ( pBlocker && pBlocker->GetHealth() > 0 && OccupyStrategySlot( SQUAD_SLOT_ATTACK_OCCLUDER ) ) + { + return SCHED_SHOOT_ENEMY_COVER; + } + } + + return SCHED_NONE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +int CNPC_Combine::TranslateSchedule( int scheduleType ) +{ + switch( scheduleType ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + { + if ( m_pSquad ) + { + // Have to explicitly check innate range attack condition as may have weapon with range attack 2 + if ( g_pGameRules->IsSkillLevel( SKILL_HARD ) && + HasCondition(COND_CAN_RANGE_ATTACK2) && + OccupyStrategySlot( SQUAD_SLOT_GRENADE1 ) ) + { + m_Sentences.Speak( "COMBINE_THROW_GRENADE" ); + return SCHED_COMBINE_TOSS_GRENADE_COVER1; + } + else + { + if ( ShouldChargePlayer() && !IsUnreachable( GetEnemy() ) ) + return SCHED_COMBINE_CHARGE_PLAYER; + + return SCHED_COMBINE_TAKE_COVER1; + } + } + else + { + // Have to explicitly check innate range attack condition as may have weapon with range attack 2 + if ( random->RandomInt(0,1) && HasCondition(COND_CAN_RANGE_ATTACK2) ) + { + return SCHED_COMBINE_GRENADE_COVER1; + } + else + { + if ( ShouldChargePlayer() && !IsUnreachable( GetEnemy() ) ) + return SCHED_COMBINE_CHARGE_PLAYER; + + return SCHED_COMBINE_TAKE_COVER1; + } + } + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return SCHED_COMBINE_TAKE_COVER_FROM_BEST_SOUND; + } + break; + case SCHED_COMBINE_TAKECOVER_FAILED: + { + if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) && OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) ) + { + return TranslateSchedule( SCHED_RANGE_ATTACK1 ); + } + + // Run somewhere randomly + return TranslateSchedule( SCHED_FAIL ); + break; + } + break; + case SCHED_FAIL_ESTABLISH_LINE_OF_FIRE: + { + if( !IsCrouching() ) + { + if( GetEnemy() && CouldShootIfCrouching( GetEnemy() ) ) + { + Crouch(); + return SCHED_COMBAT_FACE; + } + } + + if( HasCondition( COND_SEE_ENEMY ) ) + { + return TranslateSchedule( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else if ( !m_AssaultBehavior.HasAssaultCue() ) + { + // Don't patrol if I'm in the middle of an assault, because + // I'll never return to the assault. + if ( GetEnemy() ) + { + RememberUnreachable( GetEnemy() ); + } + + return TranslateSchedule( SCHED_COMBINE_PATROL ); + } + } + break; + case SCHED_COMBINE_ASSAULT: + { + CBaseEntity *pEntity = GetEnemy(); + + // FIXME: this should be generalized by the schedules that are selected, or in the definition of + // what "cover" means (i.e., trace attack vulnerability vs. physical attack vulnerability + if (pEntity && pEntity->MyNPCPointer()) + { + if ( !(pEntity->MyNPCPointer()->CapabilitiesGet( ) & bits_CAP_WEAPON_RANGE_ATTACK1)) + { + return TranslateSchedule( SCHED_ESTABLISH_LINE_OF_FIRE ); + } + } + // don't charge forward if there's a hint group + if (GetHintGroup() != NULL_STRING) + { + return TranslateSchedule( SCHED_ESTABLISH_LINE_OF_FIRE ); + } + return SCHED_COMBINE_ASSAULT; + } + case SCHED_ESTABLISH_LINE_OF_FIRE: + { + // always assume standing + // Stand(); + + if( CanAltFireEnemy(true) && OccupyStrategySlot(SQUAD_SLOT_SPECIAL_ATTACK) ) + { + // If an elite in the squad could fire a combine ball at the player's last known position, + // do so! + return SCHED_COMBINE_AR2_ALTFIRE; + } + + if( IsUsingTacticalVariant( TACTICAL_VARIANT_PRESSURE_ENEMY ) && !IsRunningBehavior() ) + { + if( OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) ) + { + return SCHED_COMBINE_PRESS_ATTACK; + } + } + + return SCHED_COMBINE_ESTABLISH_LINE_OF_FIRE; + } + break; + case SCHED_HIDE_AND_RELOAD: + { + // stand up, just in case + // Stand(); + // DesireStand(); + if( CanGrenadeEnemy() && OccupyStrategySlot( SQUAD_SLOT_GRENADE1 ) && random->RandomInt( 0, 100 ) < 20 ) + { + // If I COULD throw a grenade and I need to reload, 20% chance I'll throw a grenade before I hide to reload. + return SCHED_COMBINE_GRENADE_AND_RELOAD; + } + + // No running away in the citadel! + if ( ShouldChargePlayer() ) + return SCHED_RELOAD; + + return SCHED_COMBINE_HIDE_AND_RELOAD; + } + break; + case SCHED_RANGE_ATTACK1: + { + if ( HasCondition( COND_NO_PRIMARY_AMMO ) || HasCondition( COND_LOW_PRIMARY_AMMO ) ) + { + // Ditch the strategy slot for attacking (which we just reserved!) + VacateStrategySlot(); + return TranslateSchedule( SCHED_HIDE_AND_RELOAD ); + } + + if( CanAltFireEnemy(true) && OccupyStrategySlot(SQUAD_SLOT_SPECIAL_ATTACK) ) + { + // Since I'm holding this squadslot, no one else can try right now. If I die before the shot + // goes off, I won't have affected anyone else's ability to use this attack at their nearest + // convenience. + return SCHED_COMBINE_AR2_ALTFIRE; + } + + if ( IsCrouching() || ( CrouchIsDesired() && !HasCondition( COND_HEAVY_DAMAGE ) ) ) + { + // See if we can crouch and shoot + if (GetEnemy() != NULL) + { + float dist = (GetLocalOrigin() - GetEnemy()->GetLocalOrigin()).Length(); + + // only crouch if they are relatively far away + if (dist > COMBINE_MIN_CROUCH_DISTANCE) + { + // try crouching + Crouch(); + + Vector targetPos = GetEnemy()->BodyTarget(GetActiveWeapon()->GetLocalOrigin()); + + // if we can't see it crouched, stand up + if (!WeaponLOSCondition(GetLocalOrigin(),targetPos,false)) + { + Stand(); + } + } + } + } + else + { + // always assume standing + Stand(); + } + + return SCHED_COMBINE_RANGE_ATTACK1; + } + case SCHED_RANGE_ATTACK2: + { + // If my weapon can range attack 2 use the weapon + if (GetActiveWeapon() && GetActiveWeapon()->CapabilitiesGet() & bits_CAP_WEAPON_RANGE_ATTACK2) + { + return SCHED_RANGE_ATTACK2; + } + // Otherwise use innate attack + else + { + return SCHED_COMBINE_RANGE_ATTACK2; + } + } + // SCHED_COMBAT_FACE: + // SCHED_COMBINE_WAIT_FACE_ENEMY: + // SCHED_COMBINE_SWEEP: + // SCHED_COMBINE_COVER_AND_RELOAD: + // SCHED_COMBINE_FOUND_ENEMY: + + case SCHED_VICTORY_DANCE: + { + return SCHED_COMBINE_VICTORY_DANCE; + } + case SCHED_COMBINE_SUPPRESS: + { +#define MIN_SIGNAL_DIST 256 + if ( GetEnemy() != NULL && GetEnemy()->IsPlayer() && m_bFirstEncounter ) + { + float flDistToEnemy = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).Length(); + + if( flDistToEnemy >= MIN_SIGNAL_DIST ) + { + m_bFirstEncounter = false;// after first encounter, leader won't issue handsigns anymore when he has a new enemy + return SCHED_COMBINE_SIGNAL_SUPPRESS; + } + } + + return SCHED_COMBINE_SUPPRESS; + } + case SCHED_FAIL: + { + if ( GetEnemy() != NULL ) + { + return SCHED_COMBINE_COMBAT_FAIL; + } + return SCHED_FAIL; + } + + case SCHED_COMBINE_PATROL: + { + // If I have an enemy, don't go off into random patrol mode. + if ( GetEnemy() && GetEnemy()->IsAlive() ) + return SCHED_COMBINE_PATROL_ENEMY; + + return SCHED_COMBINE_PATROL; + } + } + + return BaseClass::TranslateSchedule( scheduleType ); +} + +//========================================================= +//========================================================= +void CNPC_Combine::OnStartSchedule( int scheduleType ) +{ +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CNPC_Combine::HandleAnimEvent( animevent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + bool handledEvent = false; + + if (pEvent->type & AE_TYPE_NEWEVENTSYSTEM) + { + if ( pEvent->event == COMBINE_AE_BEGIN_ALTFIRE ) + { + EmitSound( "Weapon_CombineGuard.Special1" ); + handledEvent = true; + } + else if ( pEvent->event == COMBINE_AE_ALTFIRE ) + { + if( IsElite() ) + { + animevent_t fakeEvent; + + fakeEvent.pSource = this; + fakeEvent.event = EVENT_WEAPON_AR2_ALTFIRE; + GetActiveWeapon()->Operator_HandleAnimEvent( &fakeEvent, this ); + + // Stop other squad members from combine balling for a while. + DelaySquadAltFireAttack( 10.0f ); + + // I'm disabling this decrementor. At the time of this change, the elites + // don't bother to check if they have grenades anyway. This means that all + // elites have infinite combine balls, even if the designer marks the elite + // as having 0 grenades. By disabling this decrementor, yet enabling the code + // that makes sure the elite has grenades in order to fire a combine ball, we + // preserve the legacy behavior while making it possible for a designer to prevent + // elites from shooting combine balls by setting grenades to '0' in hammer. (sjb) EP2_OUTLAND_10 + // m_iNumGrenades--; + } + + handledEvent = true; + } + else + { + BaseClass::HandleAnimEvent( pEvent ); + } + } + else + { + switch( pEvent->event ) + { + case COMBINE_AE_AIM: + { + handledEvent = true; + break; + } + case COMBINE_AE_RELOAD: + + // We never actually run out of ammo, just need to refill the clip + if (GetActiveWeapon()) + { + GetActiveWeapon()->WeaponSound( RELOAD_NPC ); + GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1(); + GetActiveWeapon()->m_iClip2 = GetActiveWeapon()->GetMaxClip2(); + } + ClearCondition(COND_LOW_PRIMARY_AMMO); + ClearCondition(COND_NO_PRIMARY_AMMO); + ClearCondition(COND_NO_SECONDARY_AMMO); + handledEvent = true; + break; + + case COMBINE_AE_GREN_TOSS: + { + Vector vecSpin; + vecSpin.x = random->RandomFloat( -1000.0, 1000.0 ); + vecSpin.y = random->RandomFloat( -1000.0, 1000.0 ); + vecSpin.z = random->RandomFloat( -1000.0, 1000.0 ); + + Vector vecStart; + GetAttachment( "lefthand", vecStart ); + + if( m_NPCState == NPC_STATE_SCRIPT ) + { + // Use a fixed velocity for grenades thrown in scripted state. + // Grenades thrown from a script do not count against grenades remaining for the AI to use. + Vector forward, up, vecThrow; + + GetVectors( &forward, NULL, &up ); + vecThrow = forward * 750 + up * 175; + Fraggrenade_Create( vecStart, vec3_angle, vecThrow, vecSpin, this, COMBINE_GRENADE_TIMER, true ); + } + else + { + // Use the Velocity that AI gave us. + Fraggrenade_Create( vecStart, vec3_angle, m_vecTossVelocity, vecSpin, this, COMBINE_GRENADE_TIMER, true ); + m_iNumGrenades--; + } + + // wait six seconds before even looking again to see if a grenade can be thrown. + m_flNextGrenadeCheck = gpGlobals->curtime + 6; + } + handledEvent = true; + break; + + case COMBINE_AE_GREN_LAUNCH: + { + EmitSound( "NPC_Combine.GrenadeLaunch" ); + + CBaseEntity *pGrenade = CreateNoSpawn( "npc_contactgrenade", Weapon_ShootPosition(), vec3_angle, this ); + pGrenade->KeyValue( "velocity", m_vecTossVelocity ); + pGrenade->Spawn( ); + + if ( g_pGameRules->IsSkillLevel(SKILL_HARD) ) + m_flNextGrenadeCheck = gpGlobals->curtime + random->RandomFloat( 2, 5 );// wait a random amount of time before shooting again + else + m_flNextGrenadeCheck = gpGlobals->curtime + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + } + handledEvent = true; + break; + + case COMBINE_AE_GREN_DROP: + { + Vector vecStart; + GetAttachment( "lefthand", vecStart ); + + Fraggrenade_Create( vecStart, vec3_angle, m_vecTossVelocity, vec3_origin, this, COMBINE_GRENADE_TIMER, true ); + m_iNumGrenades--; + } + handledEvent = true; + break; + + case COMBINE_AE_KICK: + { + // Does no damage, because damage is applied based upon whether the target can handle the interaction + CBaseEntity *pHurt = CheckTraceHullAttack( 70, -Vector(16,16,18), Vector(16,16,18), 0, DMG_CLUB ); + CBaseCombatCharacter* pBCC = ToBaseCombatCharacter( pHurt ); + if (pBCC) + { + Vector forward, up; + AngleVectors( GetLocalAngles(), &forward, NULL, &up ); + + if ( !pBCC->DispatchInteraction( g_interactionCombineBash, NULL, this ) ) + { + if ( pBCC->IsPlayer() ) + { + pBCC->ViewPunch( QAngle(-12,-7,0) ); + pHurt->ApplyAbsVelocityImpulse( forward * 100 + up * 50 ); + } + + CTakeDamageInfo info( this, this, m_nKickDamage, DMG_CLUB ); + CalculateMeleeDamageForce( &info, forward, pBCC->GetAbsOrigin() ); + pBCC->TakeDamage( info ); + + EmitSound( "NPC_Combine.WeaponBash" ); + } + } + + m_Sentences.Speak( "COMBINE_KICK" ); + handledEvent = true; + break; + } + + case COMBINE_AE_CAUGHT_ENEMY: + m_Sentences.Speak( "COMBINE_ALERT" ); + handledEvent = true; + break; + + default: + BaseClass::HandleAnimEvent( pEvent ); + break; + } + } + + if( handledEvent ) + { + m_iLastAnimEventHandled = pEvent->event; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Get shoot position of BCC at an arbitrary position +// Input : +// Output : +//----------------------------------------------------------------------------- +Vector CNPC_Combine::Weapon_ShootPosition( ) +{ + bool bStanding = !IsCrouching(); + Vector right; + GetVectors( NULL, &right, NULL ); + + if ((CapabilitiesGet() & bits_CAP_DUCK) ) + { + if ( IsCrouchedActivity( GetActivity() ) ) + { + bStanding = false; + } + } + + // FIXME: rename this "estimated" since it's not based on animation + // FIXME: the orientation won't be correct when testing from arbitary positions for arbitary angles + + if ( bStanding ) + { + if( HasShotgun() ) + { + return GetAbsOrigin() + COMBINE_SHOTGUN_STANDING_POSITION + right * 8; + } + else + { + return GetAbsOrigin() + COMBINE_GUN_STANDING_POSITION + right * 8; + } + } + else + { + if( HasShotgun() ) + { + return GetAbsOrigin() + COMBINE_SHOTGUN_CROUCHING_POSITION + right * 8; + } + else + { + return GetAbsOrigin() + COMBINE_GUN_CROUCHING_POSITION + right * 8; + } + } +} + + +//========================================================= +// Speak Sentence - say your cued up sentence. +// +// Some grunt sentences (take cover and charge) rely on actually +// being able to execute the intended action. It's really lame +// when a grunt says 'COVER ME' and then doesn't move. The problem +// is that the sentences were played when the decision to TRY +// to move to cover was made. Now the sentence is played after +// we know for sure that there is a valid path. The schedule +// may still fail but in most cases, well after the grunt has +// started moving. +//========================================================= +void CNPC_Combine::SpeakSentence( int sentenceType ) +{ + switch( sentenceType ) + { + case 0: // assault + AnnounceAssault(); + break; + + case 1: // Flanking the player + // If I'm moving more than 20ft, I need to talk about it + if ( GetNavigator()->GetPath()->GetPathLength() > 20 * 12.0f ) + { + m_Sentences.Speak( "COMBINE_FLANK" ); + } + break; + } +} + +//========================================================= +// PainSound +//========================================================= +void CNPC_Combine::PainSound ( void ) +{ + // NOTE: The response system deals with this at the moment + if ( GetFlags() & FL_DISSOLVING ) + return; + + if ( gpGlobals->curtime > m_flNextPainSoundTime ) + { + const char *pSentenceName = "COMBINE_PAIN"; + float healthRatio = (float)GetHealth() / (float)GetMaxHealth(); + if ( !HasMemory(bits_MEMORY_PAIN_LIGHT_SOUND) && healthRatio > 0.9 ) + { + Remember( bits_MEMORY_PAIN_LIGHT_SOUND ); + pSentenceName = "COMBINE_TAUNT"; + } + else if ( !HasMemory(bits_MEMORY_PAIN_HEAVY_SOUND) && healthRatio < 0.25 ) + { + Remember( bits_MEMORY_PAIN_HEAVY_SOUND ); + pSentenceName = "COMBINE_COVER"; + } + + m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS ); + m_flNextPainSoundTime = gpGlobals->curtime + 1; + } +} + +//----------------------------------------------------------------------------- +// Purpose: implemented by subclasses to give them an opportunity to make +// a sound when they lose their enemy +// Input : +// Output : +//----------------------------------------------------------------------------- +void CNPC_Combine::LostEnemySound( void) +{ + if ( gpGlobals->curtime <= m_flNextLostSoundTime ) + return; + + const char *pSentence; + if (!(CBaseEntity*)GetEnemy() || gpGlobals->curtime - GetEnemyLastTimeSeen() > 10) + { + pSentence = "COMBINE_LOST_LONG"; + } + else + { + pSentence = "COMBINE_LOST_SHORT"; + } + + if ( m_Sentences.Speak( pSentence ) >= 0 ) + { + m_flNextLostSoundTime = gpGlobals->curtime + random->RandomFloat(5.0,15.0); + } +} + +//----------------------------------------------------------------------------- +// Purpose: implemented by subclasses to give them an opportunity to make +// a sound when they lose their enemy +// Input : +// Output : +//----------------------------------------------------------------------------- +void CNPC_Combine::FoundEnemySound( void) +{ + m_Sentences.Speak( "COMBINE_REFIND_ENEMY", SENTENCE_PRIORITY_HIGH ); +} + +//----------------------------------------------------------------------------- +// Purpose: Implemented by subclasses to give them an opportunity to make +// a sound before they attack +// Input : +// Output : +//----------------------------------------------------------------------------- + +// BUGBUG: It looks like this is never played because combine don't do SCHED_WAKE_ANGRY or anything else that does a TASK_SOUND_WAKE +void CNPC_Combine::AlertSound( void) +{ + if ( gpGlobals->curtime > m_flNextAlertSoundTime ) + { + m_Sentences.Speak( "COMBINE_GO_ALERT", SENTENCE_PRIORITY_HIGH ); + m_flNextAlertSoundTime = gpGlobals->curtime + 10.0f; + } +} + +//========================================================= +// NotifyDeadFriend +//========================================================= +void CNPC_Combine::NotifyDeadFriend ( CBaseEntity* pFriend ) +{ + if ( GetSquad()->NumMembers() < 2 ) + { + m_Sentences.Speak( "COMBINE_LAST_OF_SQUAD", SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_NORMAL ); + JustMadeSound(); + return; + } + // relaxed visibility test so that guys say this more often + //if( FInViewCone( pFriend ) && FVisible( pFriend ) ) + { + m_Sentences.Speak( "COMBINE_MAN_DOWN" ); + } + BaseClass::NotifyDeadFriend(pFriend); +} + +//========================================================= +// DeathSound +//========================================================= +void CNPC_Combine::DeathSound ( void ) +{ + // NOTE: The response system deals with this at the moment + if ( GetFlags() & FL_DISSOLVING ) + return; + + m_Sentences.Speak( "COMBINE_DIE", SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS ); +} + +//========================================================= +// IdleSound +//========================================================= +void CNPC_Combine::IdleSound( void ) +{ + if (g_fCombineQuestion || random->RandomInt(0,1)) + { + if (!g_fCombineQuestion) + { + // ask question or make statement + switch (random->RandomInt(0,2)) + { + case 0: // check in + if ( m_Sentences.Speak( "COMBINE_CHECK" ) >= 0 ) + { + g_fCombineQuestion = 1; + } + break; + + case 1: // question + if ( m_Sentences.Speak( "COMBINE_QUEST" ) >= 0 ) + { + g_fCombineQuestion = 2; + } + break; + + case 2: // statement + m_Sentences.Speak( "COMBINE_IDLE" ); + break; + } + } + else + { + switch (g_fCombineQuestion) + { + case 1: // check in + if ( m_Sentences.Speak( "COMBINE_CLEAR" ) >= 0 ) + { + g_fCombineQuestion = 0; + } + break; + case 2: // question + if ( m_Sentences.Speak( "COMBINE_ANSWER" ) >= 0 ) + { + g_fCombineQuestion = 0; + } + break; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// +// This is for Grenade attacks. As the test for grenade attacks +// is expensive we don't want to do it every frame. Return true +// if we meet minimum set of requirements and then test for actual +// throw later if we actually decide to do a grenade attack. +// Input : +// Output : +//----------------------------------------------------------------------------- +int CNPC_Combine::RangeAttack2Conditions( float flDot, float flDist ) +{ + return COND_NONE; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if the combine has grenades, hasn't checked lately, and +// can throw a grenade at the target point. +// Input : &vecTarget - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CNPC_Combine::CanThrowGrenade( const Vector &vecTarget ) +{ + if( m_iNumGrenades < 1 ) + { + // Out of grenades! + return false; + } + + if (gpGlobals->curtime < m_flNextGrenadeCheck ) + { + // Not allowed to throw another grenade right now. + return false; + } + + float flDist; + flDist = ( vecTarget - GetAbsOrigin() ).Length(); + + if( flDist > 1024 || flDist < 128 ) + { + // Too close or too far! + m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second. + return false; + } + + // ----------------------- + // If moving, don't check. + // ----------------------- + if ( m_flGroundSpeed != 0 ) + return false; + +#if 0 + Vector vecEnemyLKP = GetEnemyLKP(); + if ( !( GetEnemy()->GetFlags() & FL_ONGROUND ) && GetEnemy()->GetWaterLevel() == 0 && vecEnemyLKP.z > (GetAbsOrigin().z + WorldAlignMaxs().z) ) + { + //!!!BUGBUG - we should make this check movetype and make sure it isn't FLY? Players who jump a lot are unlikely to + // be grenaded. + // don't throw grenades at anything that isn't on the ground! + return COND_NONE; + } +#endif + + // --------------------------------------------------------------------- + // Are any of my squad members near the intended grenade impact area? + // --------------------------------------------------------------------- + if ( m_pSquad ) + { + if (m_pSquad->SquadMemberInRange( vecTarget, COMBINE_MIN_GRENADE_CLEAR_DIST )) + { + // crap, I might blow my own guy up. Don't throw a grenade and don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second. + + // Tell my squad members to clear out so I can get a grenade in + CSoundEnt::InsertSound( SOUND_MOVE_AWAY | SOUND_CONTEXT_COMBINE_ONLY, vecTarget, COMBINE_MIN_GRENADE_CLEAR_DIST, 0.1 ); + return false; + } + } + + return CheckCanThrowGrenade( vecTarget ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the combine can throw a grenade at the specified target point +// Input : &vecTarget - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CNPC_Combine::CheckCanThrowGrenade( const Vector &vecTarget ) +{ + //NDebugOverlay::Line( EyePosition(), vecTarget, 0, 255, 0, false, 5 ); + + // --------------------------------------------------------------------- + // Check that throw is legal and clear + // --------------------------------------------------------------------- + // FIXME: this is only valid for hand grenades, not RPG's + Vector vecToss; + Vector vecMins = -Vector(4,4,4); + Vector vecMaxs = Vector(4,4,4); + if( FInViewCone( vecTarget ) && CBaseEntity::FVisible( vecTarget ) ) + { + vecToss = VecCheckThrow( this, EyePosition(), vecTarget, COMBINE_GRENADE_THROW_SPEED, 1.0, &vecMins, &vecMaxs ); + } + else + { + // Have to try a high toss. Do I have enough room? + trace_t tr; + AI_TraceLine( EyePosition(), EyePosition() + Vector( 0, 0, 64 ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + if( tr.fraction != 1.0 ) + { + return false; + } + + vecToss = VecCheckToss( this, EyePosition(), vecTarget, -1, 1.0, true, &vecMins, &vecMaxs ); + } + + if ( vecToss != vec3_origin ) + { + m_vecTossVelocity = vecToss; + + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->curtime + 1; // 1/3 second. + return true; + } + else + { + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second. + return false; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CNPC_Combine::CanAltFireEnemy( bool bUseFreeKnowledge ) +{ + if (!IsElite() ) + return false; + + if (IsCrouching()) + return false; + + if( gpGlobals->curtime < m_flNextAltFireTime ) + return false; + + if( !GetEnemy() ) + return false; + + if (gpGlobals->curtime < m_flNextGrenadeCheck ) + return false; + + // See Steve Bond if you plan on changing this next piece of code!! (SJB) EP2_OUTLAND_10 + if (m_iNumGrenades < 1) + return false; + + CBaseEntity *pEnemy = GetEnemy(); + + if( !pEnemy->IsPlayer() && (!pEnemy->IsNPC() || !pEnemy->MyNPCPointer()->IsPlayerAlly()) ) + return false; + + Vector vecTarget; + + // Determine what point we're shooting at + if( bUseFreeKnowledge ) + { + vecTarget = GetEnemies()->LastKnownPosition( pEnemy ) + (pEnemy->GetViewOffset()*0.75);// approximates the chest + } + else + { + vecTarget = GetEnemies()->LastSeenPosition( pEnemy ) + (pEnemy->GetViewOffset()*0.75);// approximates the chest + } + + // Trace a hull about the size of the combine ball (don't shoot through grates!) + trace_t tr; + + Vector mins( -12, -12, -12 ); + Vector maxs( 12, 12, 12 ); + + Vector vShootPosition = EyePosition(); + + if ( GetActiveWeapon() ) + { + GetActiveWeapon()->GetAttachment( "muzzle", vShootPosition ); + } + + // Trace a hull about the size of the combine ball. + UTIL_TraceHull( vShootPosition, vecTarget, mins, maxs, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + + float flLength = (vShootPosition - vecTarget).Length(); + + flLength *= tr.fraction; + + //If the ball can travel at least 65% of the distance to the player then let the NPC shoot it. + if( tr.fraction >= 0.65 && flLength > 128.0f ) + { + // Target is valid + m_vecAltFireTarget = vecTarget; + return true; + } + + + // Check again later + m_vecAltFireTarget = vec3_origin; + m_flNextGrenadeCheck = gpGlobals->curtime + 1.0f; + return false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CNPC_Combine::CanGrenadeEnemy( bool bUseFreeKnowledge ) +{ + if( IsElite() ) + return false; + + CBaseEntity *pEnemy = GetEnemy(); + + Assert( pEnemy != NULL ); + + if( pEnemy ) + { + // I'm not allowed to throw grenades during dustoff + if ( IsCurSchedule(SCHED_DROPSHIP_DUSTOFF) ) + return false; + + if( bUseFreeKnowledge ) + { + // throw to where we think they are. + return CanThrowGrenade( GetEnemies()->LastKnownPosition( pEnemy ) ); + } + else + { + // hafta throw to where we last saw them. + return CanThrowGrenade( GetEnemies()->LastSeenPosition( pEnemy ) ); + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: For combine melee attack (kick/hit) +// Input : +// Output : +//----------------------------------------------------------------------------- +int CNPC_Combine::MeleeAttack1Conditions ( float flDot, float flDist ) +{ + if (flDist > 64) + { + return COND_NONE; // COND_TOO_FAR_TO_ATTACK; + } + else if (flDot < 0.7) + { + return COND_NONE; // COND_NOT_FACING_ATTACK; + } + + // Check Z + if ( GetEnemy() && fabs(GetEnemy()->GetAbsOrigin().z - GetAbsOrigin().z) > 64 ) + return COND_NONE; + + if ( dynamic_cast<CBaseHeadcrab *>(GetEnemy()) != NULL ) + { + return COND_NONE; + } + + // Make sure not trying to kick through a window or something. + trace_t tr; + Vector vecSrc, vecEnd; + + vecSrc = WorldSpaceCenter(); + vecEnd = GetEnemy()->WorldSpaceCenter(); + + AI_TraceLine(vecSrc, vecEnd, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr); + if( tr.m_pEnt != GetEnemy() ) + { + return COND_NONE; + } + + return COND_CAN_MELEE_ATTACK1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Vector +//----------------------------------------------------------------------------- +Vector CNPC_Combine::EyePosition( void ) +{ + if ( !IsCrouching() ) + { + return GetAbsOrigin() + COMBINE_EYE_STANDING_POSITION; + } + else + { + return GetAbsOrigin() + COMBINE_EYE_CROUCHING_POSITION; + } + + /* + Vector m_EyePos; + GetAttachment( "eyes", m_EyePos ); + return m_EyePos; + */ +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +Vector CNPC_Combine::GetAltFireTarget() +{ + Assert( IsElite() ); + + return m_vecAltFireTarget; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : nActivity - +// Output : Vector +//----------------------------------------------------------------------------- +Vector CNPC_Combine::EyeOffset( Activity nActivity ) +{ + if (CapabilitiesGet() & bits_CAP_DUCK) + { + if ( IsCrouchedActivity( nActivity ) ) + return COMBINE_EYE_CROUCHING_POSITION; + + } + // if the hint doesn't tell anything, assume current state + if ( !IsCrouching() ) + { + return COMBINE_EYE_STANDING_POSITION; + } + else + { + return COMBINE_EYE_CROUCHING_POSITION; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Vector CNPC_Combine::GetCrouchEyeOffset( void ) +{ + return COMBINE_EYE_CROUCHING_POSITION; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Combine::SetActivity( Activity NewActivity ) +{ + BaseClass::SetActivity( NewActivity ); + + m_iLastAnimEventHandled = -1; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +NPC_STATE CNPC_Combine::SelectIdealState( void ) +{ + switch ( m_NPCState ) + { + case NPC_STATE_COMBAT: + { + if ( GetEnemy() == NULL ) + { + if ( !HasCondition( COND_ENEMY_DEAD ) ) + { + // Lost track of my enemy. Patrol. + SetCondition( COND_COMBINE_SHOULD_PATROL ); + } + return NPC_STATE_ALERT; + } + else if ( HasCondition( COND_ENEMY_DEAD ) ) + { + AnnounceEnemyKill(GetEnemy()); + } + } + + default: + { + return BaseClass::SelectIdealState(); + } + } + + return GetIdealState(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CNPC_Combine::OnBeginMoveAndShoot() +{ + if ( BaseClass::OnBeginMoveAndShoot() ) + { + if( HasStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) ) + return true; // already have the slot I need + + if( !HasStrategySlotRange( SQUAD_SLOT_GRENADE1, SQUAD_SLOT_ATTACK_OCCLUDER ) && OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) ) + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Combine::OnEndMoveAndShoot() +{ + VacateStrategySlot(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +WeaponProficiency_t CNPC_Combine::CalcWeaponProficiency( CBaseCombatWeapon *pWeapon ) +{ + if( FClassnameIs( pWeapon, "weapon_ar2" ) ) + { + if( hl2_episodic.GetBool() ) + { + return WEAPON_PROFICIENCY_VERY_GOOD; + } + else + { + return WEAPON_PROFICIENCY_GOOD; + } + } + else if( FClassnameIs( pWeapon, "weapon_shotgun" ) ) + { + if( m_nSkin != COMBINE_SKIN_SHOTGUNNER ) + { + m_nSkin = COMBINE_SKIN_SHOTGUNNER; + } + + return WEAPON_PROFICIENCY_PERFECT; + } + else if( FClassnameIs( pWeapon, "weapon_smg1" ) ) + { + return WEAPON_PROFICIENCY_GOOD; + } + + return BaseClass::CalcWeaponProficiency( pWeapon ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CNPC_Combine::HasShotgun() +{ + if( GetActiveWeapon() && GetActiveWeapon()->m_iClassname == s_iszShotgunClassname ) + { + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Only supports weapons that use clips. +//----------------------------------------------------------------------------- +bool CNPC_Combine::ActiveWeaponIsFullyLoaded() +{ + CBaseCombatWeapon *pWeapon = GetActiveWeapon(); + + if( !pWeapon ) + return false; + + if( !pWeapon->UsesClipsForAmmo1() ) + return false; + + return ( pWeapon->Clip1() >= pWeapon->GetMaxClip1() ); +} + + +//----------------------------------------------------------------------------- +// 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 : The type of interaction, extra info pointer, and who started it +// Output : true - if sub-class has a response for the interaction +// false - if sub-class has no response +//----------------------------------------------------------------------------- +bool CNPC_Combine::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter *sourceEnt) +{ + if ( interactionType == g_interactionTurretStillStanding ) + { + // A turret that I've kicked recently is still standing 5 seconds later. + if ( sourceEnt == GetEnemy() ) + { + // It's still my enemy. Time to grenade it. + Vector forward, up; + AngleVectors( GetLocalAngles(), &forward, NULL, &up ); + m_vecTossVelocity = forward * 10; + SetCondition( COND_COMBINE_DROP_GRENADE ); + ClearSchedule( "Failed to kick over turret" ); + } + return true; + } + + return BaseClass::HandleInteraction( interactionType, data, sourceEnt ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +const char* CNPC_Combine::GetSquadSlotDebugName( int iSquadSlot ) +{ + switch( iSquadSlot ) + { + case SQUAD_SLOT_GRENADE1: return "SQUAD_SLOT_GRENADE1"; + break; + case SQUAD_SLOT_GRENADE2: return "SQUAD_SLOT_GRENADE2"; + break; + case SQUAD_SLOT_ATTACK_OCCLUDER: return "SQUAD_SLOT_ATTACK_OCCLUDER"; + break; + case SQUAD_SLOT_OVERWATCH: return "SQUAD_SLOT_OVERWATCH"; + break; + } + + return BaseClass::GetSquadSlotDebugName( iSquadSlot ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CNPC_Combine::IsUsingTacticalVariant( int variant ) +{ + if( variant == TACTICAL_VARIANT_PRESSURE_ENEMY && m_iTacticalVariant == TACTICAL_VARIANT_PRESSURE_ENEMY_UNTIL_CLOSE ) + { + // Essentially, fib. Just say that we are a 'pressure enemy' soldier. + return true; + } + + return m_iTacticalVariant == variant; +} + +//----------------------------------------------------------------------------- +// For the purpose of determining whether to use a pathfinding variant, this +// function determines whether the current schedule is a schedule that +// 'approaches' the enemy. +//----------------------------------------------------------------------------- +bool CNPC_Combine::IsRunningApproachEnemySchedule() +{ + if( IsCurSchedule( SCHED_CHASE_ENEMY ) ) + return true; + + if( IsCurSchedule( SCHED_ESTABLISH_LINE_OF_FIRE ) ) + return true; + + if( IsCurSchedule( SCHED_COMBINE_PRESS_ATTACK, false ) ) + return true; + + return false; +} + +bool CNPC_Combine::ShouldPickADeathPose( void ) +{ + return !IsCrouching(); +} + +//----------------------------------------------------------------------------- +// +// Schedules +// +//----------------------------------------------------------------------------- + +AI_BEGIN_CUSTOM_NPC( npc_combine, CNPC_Combine ) + +//Tasks +DECLARE_TASK( TASK_COMBINE_FACE_TOSS_DIR ) +DECLARE_TASK( TASK_COMBINE_IGNORE_ATTACKS ) +DECLARE_TASK( TASK_COMBINE_SIGNAL_BEST_SOUND ) +DECLARE_TASK( TASK_COMBINE_DEFER_SQUAD_GRENADES ) +DECLARE_TASK( TASK_COMBINE_CHASE_ENEMY_CONTINUOUSLY ) +DECLARE_TASK( TASK_COMBINE_DIE_INSTANTLY ) +DECLARE_TASK( TASK_COMBINE_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET ) +DECLARE_TASK( TASK_COMBINE_GET_PATH_TO_FORCED_GREN_LOS ) +DECLARE_TASK( TASK_COMBINE_SET_STANDING ) + +//Activities +DECLARE_ACTIVITY( ACT_COMBINE_THROW_GRENADE ) +DECLARE_ACTIVITY( ACT_COMBINE_LAUNCH_GRENADE ) +DECLARE_ACTIVITY( ACT_COMBINE_BUGBAIT ) +DECLARE_ACTIVITY( ACT_COMBINE_AR2_ALTFIRE ) +DECLARE_ACTIVITY( ACT_WALK_EASY ) +DECLARE_ACTIVITY( ACT_WALK_MARCH ) + +DECLARE_ANIMEVENT( COMBINE_AE_BEGIN_ALTFIRE ) +DECLARE_ANIMEVENT( COMBINE_AE_ALTFIRE ) + +DECLARE_SQUADSLOT( SQUAD_SLOT_GRENADE1 ) +DECLARE_SQUADSLOT( SQUAD_SLOT_GRENADE2 ) + +DECLARE_CONDITION( COND_COMBINE_NO_FIRE ) +DECLARE_CONDITION( COND_COMBINE_DEAD_FRIEND ) +DECLARE_CONDITION( COND_COMBINE_SHOULD_PATROL ) +DECLARE_CONDITION( COND_COMBINE_HIT_BY_BUGBAIT ) +DECLARE_CONDITION( COND_COMBINE_DROP_GRENADE ) +DECLARE_CONDITION( COND_COMBINE_ON_FIRE ) +DECLARE_CONDITION( COND_COMBINE_ATTACK_SLOT_AVAILABLE ) + +DECLARE_INTERACTION( g_interactionCombineBash ); + +//========================================================= +// SCHED_COMBINE_TAKE_COVER_FROM_BEST_SOUND +// +// hide from the loudest sound source (to run from grenade) +//========================================================= +DEFINE_SCHEDULE +( + SCHED_COMBINE_TAKE_COVER_FROM_BEST_SOUND, + + " Tasks" + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBINE_RUN_AWAY_FROM_BEST_SOUND" + " TASK_STOP_MOVING 0" + " TASK_COMBINE_SIGNAL_BEST_SOUND 0" + " TASK_FIND_COVER_FROM_BEST_SOUND 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_REMEMBER MEMORY:INCOVER" + " TASK_FACE_REASONABLE 0" + "" + " Interrupts" + ) + + DEFINE_SCHEDULE + ( + SCHED_COMBINE_RUN_AWAY_FROM_BEST_SOUND, + + " Tasks" + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COWER" + " TASK_GET_PATH_AWAY_FROM_BEST_SOUND 600" + " TASK_RUN_PATH_TIMED 2" + " TASK_STOP_MOVING 0" + "" + " Interrupts" + ) + //========================================================= + // SCHED_COMBINE_COMBAT_FAIL + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_COMBAT_FAIL, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE " + " TASK_WAIT_FACE_ENEMY 2" + " TASK_WAIT_PVS 0" + "" + " Interrupts" + " COND_CAN_RANGE_ATTACK1" + " COND_CAN_RANGE_ATTACK2" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_MELEE_ATTACK2" + ) + + //========================================================= + // SCHED_COMBINE_VICTORY_DANCE + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_VICTORY_DANCE, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_FACE_ENEMY 0" + " TASK_WAIT 1.5" + " TASK_GET_PATH_TO_ENEMY_CORPSE 0" + " TASK_WALK_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_FACE_ENEMY 0" + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_VICTORY_DANCE" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_LIGHT_DAMAGE" + " COND_HEAVY_DAMAGE" + ) + + //========================================================= + // SCHED_COMBINE_ASSAULT + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_ASSAULT, + + " Tasks " + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBINE_ESTABLISH_LINE_OF_FIRE" + " TASK_SET_TOLERANCE_DISTANCE 48" + " TASK_GET_PATH_TO_ENEMY_LKP 0" + " TASK_COMBINE_IGNORE_ATTACKS 0.2" + " TASK_SPEAK_SENTENCE 0" + " TASK_RUN_PATH 0" + // " TASK_COMBINE_MOVE_AND_AIM 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_COMBINE_IGNORE_ATTACKS 0.0" + "" + " Interrupts " + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + " COND_ENEMY_UNREACHABLE" + " COND_CAN_RANGE_ATTACK1" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_RANGE_ATTACK2" + " COND_CAN_MELEE_ATTACK2" + " COND_TOO_FAR_TO_ATTACK" + " COND_HEAR_DANGER" + " COND_HEAR_MOVE_AWAY" + ) + + DEFINE_SCHEDULE + ( + SCHED_COMBINE_ESTABLISH_LINE_OF_FIRE, + + " Tasks " + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_FAIL_ESTABLISH_LINE_OF_FIRE" + " TASK_SET_TOLERANCE_DISTANCE 48" + " TASK_GET_PATH_TO_ENEMY_LKP_LOS 0" + " TASK_COMBINE_SET_STANDING 1" + " TASK_SPEAK_SENTENCE 1" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_COMBINE_IGNORE_ATTACKS 0.0" + " TASK_SET_SCHEDULE SCHEDULE:SCHED_COMBAT_FACE" + " " + " Interrupts " + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + //" COND_CAN_RANGE_ATTACK1" + //" COND_CAN_RANGE_ATTACK2" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_MELEE_ATTACK2" + " COND_HEAR_DANGER" + " COND_HEAR_MOVE_AWAY" + " COND_HEAVY_DAMAGE" + ) + + //========================================================= + // SCHED_COMBINE_PRESS_ATTACK + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_PRESS_ATTACK, + + " Tasks " + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBINE_ESTABLISH_LINE_OF_FIRE" + " TASK_SET_TOLERANCE_DISTANCE 72" + " TASK_GET_PATH_TO_ENEMY_LKP 0" + " TASK_COMBINE_SET_STANDING 1" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + "" + " Interrupts " + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + " COND_ENEMY_UNREACHABLE" + " COND_NO_PRIMARY_AMMO" + " COND_LOW_PRIMARY_AMMO" + " COND_TOO_CLOSE_TO_ATTACK" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_MELEE_ATTACK2" + " COND_HEAR_DANGER" + " COND_HEAR_MOVE_AWAY" + ) + + //========================================================= + // SCHED_COMBINE_COMBAT_FACE + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_COMBAT_FACE, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" + " TASK_FACE_ENEMY 0" + " TASK_WAIT 1.5" + //" TASK_SET_SCHEDULE SCHEDULE:SCHED_COMBINE_SWEEP" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + " COND_CAN_RANGE_ATTACK1" + " COND_CAN_RANGE_ATTACK2" + ) + + //========================================================= + // SCHED_HIDE_AND_RELOAD + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_HIDE_AND_RELOAD, + + " Tasks" + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_RELOAD" + " TASK_FIND_COVER_FROM_ENEMY 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_REMEMBER MEMORY:INCOVER" + " TASK_FACE_ENEMY 0" + " TASK_RELOAD 0" + "" + " Interrupts" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_MELEE_ATTACK2" + " COND_HEAVY_DAMAGE" + " COND_HEAR_DANGER" + " COND_HEAR_MOVE_AWAY" + ) + + //========================================================= + // SCHED_COMBINE_SIGNAL_SUPPRESS + // don't stop shooting until the clip is + // empty or combine gets hurt. + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_SIGNAL_SUPPRESS, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_FACE_IDEAL 0" + " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_SIGNAL_GROUP" + " TASK_COMBINE_SET_STANDING 0" + " TASK_RANGE_ATTACK1 0" + "" + " Interrupts" + " COND_ENEMY_DEAD" + " COND_LIGHT_DAMAGE" + " COND_HEAVY_DAMAGE" + " COND_NO_PRIMARY_AMMO" + " COND_WEAPON_BLOCKED_BY_FRIEND" + " COND_WEAPON_SIGHT_OCCLUDED" + " COND_HEAR_DANGER" + " COND_HEAR_MOVE_AWAY" + " COND_COMBINE_NO_FIRE" + ) + + //========================================================= + // SCHED_COMBINE_SUPPRESS + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_SUPPRESS, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_FACE_ENEMY 0" + " TASK_COMBINE_SET_STANDING 0" + " TASK_RANGE_ATTACK1 0" + "" + " Interrupts" + " COND_ENEMY_DEAD" + " COND_LIGHT_DAMAGE" + " COND_HEAVY_DAMAGE" + " COND_NO_PRIMARY_AMMO" + " COND_HEAR_DANGER" + " COND_HEAR_MOVE_AWAY" + " COND_COMBINE_NO_FIRE" + " COND_WEAPON_BLOCKED_BY_FRIEND" + ) + + //========================================================= + // SCHED_COMBINE_ENTER_OVERWATCH + // + // Parks a combine soldier in place looking at the player's + // last known position, ready to attack if the player pops out + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_ENTER_OVERWATCH, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_COMBINE_SET_STANDING 0" + " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" + " TASK_FACE_ENEMY 0" + " TASK_SET_SCHEDULE SCHEDULE:SCHED_COMBINE_OVERWATCH" + "" + " Interrupts" + " COND_HEAR_DANGER" + " COND_NEW_ENEMY" + ) + + //========================================================= + // SCHED_COMBINE_OVERWATCH + // + // Parks a combine soldier in place looking at the player's + // last known position, ready to attack if the player pops out + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_OVERWATCH, + + " Tasks" + " TASK_WAIT_FACE_ENEMY 10" + "" + " Interrupts" + " COND_CAN_RANGE_ATTACK1" + " COND_ENEMY_DEAD" + " COND_LIGHT_DAMAGE" + " COND_HEAVY_DAMAGE" + " COND_NO_PRIMARY_AMMO" + " COND_HEAR_DANGER" + " COND_HEAR_MOVE_AWAY" + " COND_NEW_ENEMY" + ) + + //========================================================= + // SCHED_COMBINE_WAIT_IN_COVER + // we don't allow danger or the ability + // to attack to break a combine's run to cover schedule but + // when a combine is in cover we do want them to attack if they can. + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_WAIT_IN_COVER, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_COMBINE_SET_STANDING 0" + " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Translated to cover + " TASK_WAIT_FACE_ENEMY 1" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_CAN_RANGE_ATTACK1" + " COND_CAN_RANGE_ATTACK2" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_MELEE_ATTACK2" + " COND_HEAR_DANGER" + " COND_HEAR_MOVE_AWAY" + " COND_COMBINE_ATTACK_SLOT_AVAILABLE" + ) + + //========================================================= + // SCHED_COMBINE_TAKE_COVER1 + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_TAKE_COVER1 , + + " Tasks" + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBINE_TAKECOVER_FAILED" + " TASK_STOP_MOVING 0" + " TASK_WAIT 0.2" + " TASK_FIND_COVER_FROM_ENEMY 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_REMEMBER MEMORY:INCOVER" + " TASK_SET_SCHEDULE SCHEDULE:SCHED_COMBINE_WAIT_IN_COVER" + "" + " Interrupts" + ) + + DEFINE_SCHEDULE + ( + SCHED_COMBINE_TAKECOVER_FAILED, + + " Tasks" + " TASK_STOP_MOVING 0" + "" + " Interrupts" + ) + + //========================================================= + // SCHED_COMBINE_GRENADE_COVER1 + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_GRENADE_COVER1, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_FIND_COVER_FROM_ENEMY 99" + " TASK_FIND_FAR_NODE_COVER_FROM_ENEMY 384" + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SPECIAL_ATTACK2" + " TASK_CLEAR_MOVE_WAIT 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_SET_SCHEDULE SCHEDULE:SCHED_COMBINE_WAIT_IN_COVER" + "" + " Interrupts" + ) + + //========================================================= + // SCHED_COMBINE_TOSS_GRENADE_COVER1 + // + // drop grenade then run to cover. + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_TOSS_GRENADE_COVER1, + + " Tasks" + " TASK_FACE_ENEMY 0" + " TASK_RANGE_ATTACK2 0" + " TASK_SET_SCHEDULE SCHEDULE:SCHED_TAKE_COVER_FROM_ENEMY" + "" + " Interrupts" + ) + + //========================================================= + // SCHED_COMBINE_RANGE_ATTACK1 + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_RANGE_ATTACK1, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_FACE_ENEMY 0" + " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack + " TASK_WAIT_RANDOM 0.3" + " TASK_RANGE_ATTACK1 0" + " TASK_COMBINE_IGNORE_ATTACKS 0.5" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + " COND_HEAVY_DAMAGE" + " COND_LIGHT_DAMAGE" + " COND_LOW_PRIMARY_AMMO" + " COND_NO_PRIMARY_AMMO" + " COND_WEAPON_BLOCKED_BY_FRIEND" + " COND_TOO_CLOSE_TO_ATTACK" + " COND_GIVE_WAY" + " COND_HEAR_DANGER" + " COND_HEAR_MOVE_AWAY" + " COND_COMBINE_NO_FIRE" + "" + // Enemy_Occluded Don't interrupt on this. Means + // comibine will fire where player was after + // he has moved for a little while. Good effect!! + // WEAPON_SIGHT_OCCLUDED Don't block on this! Looks better for railings, etc. + ) + + //========================================================= + // AR2 Alt Fire Attack + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_AR2_ALTFIRE, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_ANNOUNCE_ATTACK 1" + " TASK_COMBINE_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET ACTIVITY:ACT_COMBINE_AR2_ALTFIRE" + "" + " Interrupts" + ) + + //========================================================= + // Mapmaker forced grenade throw + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_FORCED_GRENADE_THROW, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_COMBINE_FACE_TOSS_DIR 0" + " TASK_ANNOUNCE_ATTACK 2" // 2 = grenade + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_RANGE_ATTACK2" + " TASK_COMBINE_DEFER_SQUAD_GRENADES 0" + "" + " Interrupts" + ) + + //========================================================= + // Move to LOS of the mapmaker's forced grenade throw target + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_MOVE_TO_FORCED_GREN_LOS, + + " Tasks " + " TASK_SET_TOLERANCE_DISTANCE 48" + " TASK_COMBINE_GET_PATH_TO_FORCED_GREN_LOS 0" + " TASK_SPEAK_SENTENCE 1" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " " + " Interrupts " + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_MELEE_ATTACK2" + " COND_HEAR_DANGER" + " COND_HEAR_MOVE_AWAY" + " COND_HEAVY_DAMAGE" + ) + + //========================================================= + // SCHED_COMBINE_RANGE_ATTACK2 + // + // secondary range attack. Overriden because base class stops attacking when the enemy is occluded. + // combines's grenade toss requires the enemy be occluded. + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_RANGE_ATTACK2, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_COMBINE_FACE_TOSS_DIR 0" + " TASK_ANNOUNCE_ATTACK 2" // 2 = grenade + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_RANGE_ATTACK2" + " TASK_COMBINE_DEFER_SQUAD_GRENADES 0" + " TASK_SET_SCHEDULE SCHEDULE:SCHED_COMBINE_WAIT_IN_COVER" // don't run immediately after throwing grenade. + "" + " Interrupts" + ) + + + //========================================================= + // Throw a grenade, then run off and reload. + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_GRENADE_AND_RELOAD, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_COMBINE_FACE_TOSS_DIR 0" + " TASK_ANNOUNCE_ATTACK 2" // 2 = grenade + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_RANGE_ATTACK2" + " TASK_COMBINE_DEFER_SQUAD_GRENADES 0" + " TASK_SET_SCHEDULE SCHEDULE:SCHED_HIDE_AND_RELOAD" // don't run immediately after throwing grenade. + "" + " Interrupts" + ) + + DEFINE_SCHEDULE + ( + SCHED_COMBINE_PATROL, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_WANDER 900540" + " TASK_WALK_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_STOP_MOVING 0" + " TASK_FACE_REASONABLE 0" + " TASK_WAIT 3" + " TASK_WAIT_RANDOM 3" + " TASK_SET_SCHEDULE SCHEDULE:SCHED_COMBINE_PATROL" // keep doing it + "" + " Interrupts" + " COND_ENEMY_DEAD" + " COND_LIGHT_DAMAGE" + " COND_HEAVY_DAMAGE" + " COND_HEAR_DANGER" + " COND_HEAR_MOVE_AWAY" + " COND_NEW_ENEMY" + " COND_SEE_ENEMY" + " COND_CAN_RANGE_ATTACK1" + " COND_CAN_RANGE_ATTACK2" + ) + + DEFINE_SCHEDULE + ( + SCHED_COMBINE_BUGBAIT_DISTRACTION, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_RESET_ACTIVITY 0" + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_COMBINE_BUGBAIT" + "" + " Interrupts" + "" + ) + + //========================================================= + // SCHED_COMBINE_CHARGE_TURRET + // + // Used to run straight at enemy turrets to knock them over. + // Prevents squadmates from throwing grenades during. + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_CHARGE_TURRET, + + " Tasks" + " TASK_COMBINE_DEFER_SQUAD_GRENADES 0" + " TASK_STOP_MOVING 0" + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED" + " TASK_GET_CHASE_PATH_TO_ENEMY 300" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_FACE_ENEMY 0" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + " COND_ENEMY_UNREACHABLE" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_MELEE_ATTACK2" + " COND_TOO_CLOSE_TO_ATTACK" + " COND_TASK_FAILED" + " COND_LOST_ENEMY" + " COND_BETTER_WEAPON_AVAILABLE" + " COND_HEAR_DANGER" + ) + + //========================================================= + // SCHED_COMBINE_CHARGE_PLAYER + // + // Used to run straight at enemy player since physgun combat + // is more fun when the enemies are close + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_CHARGE_PLAYER, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED" + " TASK_COMBINE_CHASE_ENEMY_CONTINUOUSLY 192" + " TASK_FACE_ENEMY 0" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + " COND_ENEMY_UNREACHABLE" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_MELEE_ATTACK2" + " COND_TASK_FAILED" + " COND_LOST_ENEMY" + " COND_HEAR_DANGER" + ) + + //========================================================= + // SCHED_COMBINE_DROP_GRENADE + // + // Place a grenade at my feet + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_DROP_GRENADE, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SPECIAL_ATTACK2" + " TASK_FIND_COVER_FROM_ENEMY 99" + " TASK_FIND_FAR_NODE_COVER_FROM_ENEMY 384" + " TASK_CLEAR_MOVE_WAIT 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + "" + " Interrupts" + ) + + //========================================================= + // SCHED_COMBINE_PATROL_ENEMY + // + // Used instead if SCHED_COMBINE_PATROL if I have an enemy. + // Wait for the enemy a bit in the hopes of ambushing him. + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_COMBINE_PATROL_ENEMY, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_WAIT_FACE_ENEMY 1" + " TASK_WAIT_FACE_ENEMY_RANDOM 3" + "" + " Interrupts" + " COND_ENEMY_DEAD" + " COND_LIGHT_DAMAGE" + " COND_HEAVY_DAMAGE" + " COND_HEAR_DANGER" + " COND_HEAR_MOVE_AWAY" + " COND_NEW_ENEMY" + " COND_SEE_ENEMY" + " COND_CAN_RANGE_ATTACK1" + " COND_CAN_RANGE_ATTACK2" + ) + + DEFINE_SCHEDULE + ( + SCHED_COMBINE_BURNING_STAND, + + " Tasks" + " TASK_SET_ACTIVITY ACTIVITY:ACT_COMBINE_BUGBAIT" + " TASK_RANDOMIZE_FRAMERATE 20" + " TASK_WAIT 2" + " TASK_WAIT_RANDOM 3" + " TASK_COMBINE_DIE_INSTANTLY DMG_BURN" + " TASK_WAIT 1.0" + " " + " Interrupts" + ) + + DEFINE_SCHEDULE + ( + SCHED_COMBINE_FACE_IDEAL_YAW, + + " Tasks" + " TASK_FACE_IDEAL 0" + " " + " Interrupts" + ) + + DEFINE_SCHEDULE + ( + SCHED_COMBINE_MOVE_TO_MELEE, + + " Tasks" + " TASK_STORE_ENEMY_POSITION_IN_SAVEPOSITION 0" + " TASK_GET_PATH_TO_SAVEPOSITION 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " " + " Interrupts" + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + " COND_CAN_MELEE_ATTACK1" + ) + + AI_END_CUSTOM_NPC() |