diff options
Diffstat (limited to 'mp/src/game/server/hl2/npc_basescanner.cpp')
| -rw-r--r-- | mp/src/game/server/hl2/npc_basescanner.cpp | 3694 |
1 files changed, 1847 insertions, 1847 deletions
diff --git a/mp/src/game/server/hl2/npc_basescanner.cpp b/mp/src/game/server/hl2/npc_basescanner.cpp index 2fcf54b2..b05441db 100644 --- a/mp/src/game/server/hl2/npc_basescanner.cpp +++ b/mp/src/game/server/hl2/npc_basescanner.cpp @@ -1,1847 +1,1847 @@ -//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================
-
-#include "cbase.h"
-#include "npcevent.h"
-#include "ai_basenpc_physicsflyer.h"
-#include "weapon_physcannon.h"
-#include "hl2_player.h"
-#include "npc_scanner.h"
-#include "IEffects.h"
-#include "explode.h"
-#include "ai_route.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-ConVar g_debug_basescanner( "g_debug_basescanner", "0", FCVAR_CHEAT );
-
-BEGIN_DATADESC( CNPC_BaseScanner )
- DEFINE_EMBEDDED( m_KilledInfo ),
- DEFINE_SOUNDPATCH( m_pEngineSound ),
-
- DEFINE_FIELD( m_flFlyNoiseBase, FIELD_FLOAT ),
- DEFINE_FIELD( m_flEngineStallTime, FIELD_TIME ),
- DEFINE_FIELD( m_fNextFlySoundTime, FIELD_TIME ),
- DEFINE_FIELD( m_nFlyMode, FIELD_INTEGER ),
-
- DEFINE_FIELD( m_vecDiveBombDirection, FIELD_VECTOR ),
- DEFINE_FIELD( m_flDiveBombRollForce, FIELD_FLOAT ),
-
- // Physics Influence
- DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ),
- DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ),
-
- DEFINE_FIELD( m_flGoalOverrideDistance, FIELD_FLOAT ),
-
- DEFINE_FIELD( m_flAttackNearDist, FIELD_FLOAT ),
- DEFINE_FIELD( m_flAttackFarDist, FIELD_FLOAT ),
- DEFINE_FIELD( m_flAttackRange, FIELD_FLOAT ),
-
- DEFINE_FIELD( m_nPoseTail, FIELD_INTEGER ),
- DEFINE_FIELD( m_nPoseDynamo, FIELD_INTEGER ),
- DEFINE_FIELD( m_nPoseFlare, FIELD_INTEGER ),
- DEFINE_FIELD( m_nPoseFaceVert, FIELD_INTEGER ),
- DEFINE_FIELD( m_nPoseFaceHoriz, FIELD_INTEGER ),
-
- // DEFINE_FIELD( m_bHasSpoken, FIELD_BOOLEAN ),
-
- DEFINE_FIELD( m_pSmokeTrail, FIELD_CLASSPTR ),
-
- DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDistanceOverride", InputSetDistanceOverride ),
- DEFINE_INPUTFUNC( FIELD_INTEGER, "SetFlightSpeed", InputSetFlightSpeed ),
-
- DEFINE_THINKFUNC( DiveBombSoundThink ),
-END_DATADESC()
-
-ConVar sk_scanner_dmg_dive( "sk_scanner_dmg_dive","0");
-
-//-----------------------------------------------------------------------------
-// Think contexts
-//-----------------------------------------------------------------------------
-static const char *s_pDiveBombSoundThinkContext = "DiveBombSoundThinkContext";
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-CNPC_BaseScanner::CNPC_BaseScanner()
-{
-#ifdef _DEBUG
- m_vCurrentBanking.Init();
-#endif
- m_pEngineSound = NULL;
- m_bHasSpoken = false;
-
- m_flAttackNearDist = SCANNER_ATTACK_NEAR_DIST;
- m_flAttackFarDist = SCANNER_ATTACK_FAR_DIST;
- m_flAttackRange = SCANNER_ATTACK_RANGE;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::Spawn(void)
-{
-#ifdef _XBOX
- // Always fade the corpse
- AddSpawnFlags( SF_NPC_FADE_CORPSE );
- AddEffects( EF_NOSHADOW );
-#endif // _XBOX
-
- SetHullType( HULL_TINY_CENTERED );
- SetHullSizeNormal();
-
- SetSolid( SOLID_BBOX );
- AddSolidFlags( FSOLID_NOT_STANDABLE );
-
- SetMoveType( MOVETYPE_VPHYSICS );
-
- m_bloodColor = DONT_BLEED;
- SetViewOffset( Vector(0, 0, 10) ); // Position of the eyes relative to NPC's origin.
- m_flFieldOfView = 0.2;
- m_NPCState = NPC_STATE_NONE;
-
- SetNavType( NAV_FLY );
-
- AddFlag( FL_FLY );
-
- // This entity cannot be dissolved by the combine balls,
- // nor does it get killed by the mega physcannon.
- AddEFlags( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL );
-
- m_flGoalOverrideDistance = 0.0f;
-
- m_nFlyMode = SCANNER_FLY_PATROL;
- AngleVectors( GetLocalAngles(), &m_vCurrentBanking );
- m_fHeadYaw = 0;
- m_pSmokeTrail = NULL;
-
- SetCurrentVelocity( vec3_origin );
-
- // Noise modifier
- Vector bobAmount;
- bobAmount.x = random->RandomFloat( -2.0f, 2.0f );
- bobAmount.y = random->RandomFloat( -2.0f, 2.0f );
- bobAmount.z = random->RandomFloat( 2.0f, 4.0f );
- if ( random->RandomInt( 0, 1 ) )
- {
- bobAmount.z *= -1.0f;
- }
- SetNoiseMod( bobAmount );
-
- // set flight speed
- m_flSpeed = GetMaxSpeed();
-
- // --------------------------------------------
-
- CapabilitiesAdd( bits_CAP_MOVE_FLY | bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_SKIP_NAV_GROUND_CHECK );
-
- NPCInit();
-
- m_flFlyNoiseBase = random->RandomFloat( 0, M_PI );
-
- m_flNextAttack = gpGlobals->curtime;
-}
-
-//-----------------------------------------------------------------------------
-//
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::UpdateEfficiency( bool bInPVS )
-{
- SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL );
- SetMoveEfficiency( AIME_NORMAL );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-float CNPC_BaseScanner::GetAutoAimRadius()
-{
- if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE )
- {
- return 24.0f;
- }
-
- return 12.0f;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Called just before we are deleted.
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::UpdateOnRemove( void )
-{
- // Stop combat loops if I'm alive. If I'm dead, the die sound will already have stopped it.
- if ( IsAlive() && m_bHasSpoken )
- {
- SentenceStop();
- }
-
- BaseClass::UpdateOnRemove();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Gets the appropriate next schedule based on current condition
-// bits.
-//-----------------------------------------------------------------------------
-int CNPC_BaseScanner::SelectSchedule(void)
-{
- // ----------------------------------------------------
- // If I'm dead, go into a dive bomb
- // ----------------------------------------------------
- if ( m_iHealth <= 0 )
- {
- m_flSpeed = SCANNER_MAX_DIVE_BOMB_SPEED;
- return SCHED_SCANNER_ATTACK_DIVEBOMB;
- }
-
- // -------------------------------
- // If I'm in a script sequence
- // -------------------------------
- if ( m_NPCState == NPC_STATE_SCRIPT )
- return(BaseClass::SelectSchedule());
-
- // -------------------------------
- // Flinch
- // -------------------------------
- if ( HasCondition(COND_LIGHT_DAMAGE) || HasCondition(COND_HEAVY_DAMAGE) )
- {
- if ( IsHeldByPhyscannon( ) )
- return SCHED_SMALL_FLINCH;
-
- if ( m_NPCState == NPC_STATE_IDLE )
- return SCHED_SMALL_FLINCH;
-
- if ( m_NPCState == NPC_STATE_ALERT )
- {
- if ( m_iHealth < ( 3 * m_iMaxHealth / 4 ))
- return SCHED_TAKE_COVER_FROM_ORIGIN;
-
- if ( SelectWeightedSequence( ACT_SMALL_FLINCH ) != -1 )
- return SCHED_SMALL_FLINCH;
- }
- else
- {
- if ( random->RandomInt( 0, 10 ) < 4 )
- return SCHED_SMALL_FLINCH;
- }
- }
-
- // I'm being held by the physcannon... struggle!
- if ( IsHeldByPhyscannon( ) )
- return SCHED_SCANNER_HELD_BY_PHYSCANNON;
-
- // ----------------------------------------------------------
- // If I have an enemy
- // ----------------------------------------------------------
- if ( GetEnemy() != NULL && GetEnemy()->IsAlive() )
- {
- // Patrol if the enemy has vanished
- if ( HasCondition( COND_LOST_ENEMY ) )
- return SCHED_SCANNER_PATROL;
-
- // Chase via route if we're directly blocked
- if ( HasCondition( COND_SCANNER_FLY_BLOCKED ) )
- return SCHED_SCANNER_CHASE_ENEMY;
-
- // Attack if it's time
- if ( gpGlobals->curtime >= m_flNextAttack )
- {
- if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
- return SCHED_SCANNER_ATTACK;
- }
-
- // Otherwise fly in low for attack
- return SCHED_SCANNER_ATTACK_HOVER;
- }
-
- // Default to patrolling around
- return SCHED_SCANNER_PATROL;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::OnScheduleChange( void )
-{
- m_flSpeed = GetMaxSpeed();
-
- BaseClass::OnScheduleChange();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: For innate melee attack
-//-----------------------------------------------------------------------------
-int CNPC_BaseScanner::MeleeAttack1Conditions( float flDot, float flDist )
-{
- if (GetEnemy() == NULL)
- {
- return COND_NONE;
- }
-
- // Check too far to attack with 2D distance
- float vEnemyDist2D = (GetEnemy()->GetLocalOrigin() - GetLocalOrigin()).Length2D();
-
- if (m_flNextAttack > gpGlobals->curtime)
- {
- return COND_NONE;
- }
- else if (vEnemyDist2D > m_flAttackRange)
- {
- return COND_TOO_FAR_TO_ATTACK;
- }
- else if (flDot < 0.7)
- {
- return COND_NOT_FACING_ATTACK;
- }
- return COND_CAN_MELEE_ATTACK1;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : eOldState -
-// eNewState -
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::OnStateChange( NPC_STATE eOldState, NPC_STATE eNewState )
-{
- if (( eNewState == NPC_STATE_ALERT ) || ( eNewState == NPC_STATE_COMBAT ))
- {
- SetPoseParameter(m_nPoseFlare, 1.0f);
- }
- else
- {
- SetPoseParameter(m_nPoseFlare, 0);
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : pTask -
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::StartTask( const Task_t *pTask )
-{
- switch (pTask->iTask)
- {
- case TASK_SCANNER_SET_FLY_PATROL:
- {
- // Fly in patrol mode and clear any
- // remaining target entity
- m_nFlyMode = SCANNER_FLY_PATROL;
- TaskComplete();
- break;
- }
- case TASK_SCANNER_SET_FLY_CHASE:
- {
- m_nFlyMode = SCANNER_FLY_CHASE;
- TaskComplete();
- break;
- }
- case TASK_SCANNER_SET_FLY_ATTACK:
- {
- m_nFlyMode = SCANNER_FLY_ATTACK;
- TaskComplete();
- break;
- }
-
- case TASK_SCANNER_SET_FLY_DIVE:
- {
- // Pick a direction to divebomb.
- if ( GetEnemy() != NULL )
- {
- // Fly towards my enemy
- Vector vEnemyPos = GetEnemyLKP();
- m_vecDiveBombDirection = vEnemyPos - GetLocalOrigin();
- }
- else
- {
- // Pick a random forward and down direction.
- Vector forward;
- GetVectors( &forward, NULL, NULL );
- m_vecDiveBombDirection = forward + Vector( random->RandomFloat( -10, 10 ), random->RandomFloat( -10, 10 ), random->RandomFloat( -20, -10 ) );
- }
- VectorNormalize( m_vecDiveBombDirection );
-
- // Calculate a roll force.
- m_flDiveBombRollForce = random->RandomFloat( 20.0, 420.0 );
- if ( random->RandomInt( 0, 1 ) )
- {
- m_flDiveBombRollForce *= -1;
- }
-
- DiveBombSoundThink();
-
- m_nFlyMode = SCANNER_FLY_DIVE;
- TaskComplete();
- break;
- }
-
- default:
- BaseClass::StartTask(pTask);
- break;
- }
-}
-
-//------------------------------------------------------------------------------
-// Purpose: Override to split in two when attacked
-//------------------------------------------------------------------------------
-int CNPC_BaseScanner::OnTakeDamage_Alive( const CTakeDamageInfo &info )
-{
- // Start smoking when we're nearly dead
- if ( m_iHealth < ( m_iMaxHealth - ( m_iMaxHealth / 4 ) ) )
- {
- StartSmokeTrail();
- }
-
- return (BaseClass::OnTakeDamage_Alive( info ));
-}
-
-//------------------------------------------------------------------------------
-// Purpose: Override to split in two when attacked
-//------------------------------------------------------------------------------
-int CNPC_BaseScanner::OnTakeDamage_Dying( const CTakeDamageInfo &info )
-{
- // do the damage
- m_iHealth -= info.GetDamage();
-
- if ( m_iHealth < -40 )
- {
- Gib();
- return 1;
- }
-
- return VPhysicsTakeDamage( info );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
-{
- if ( info.GetDamageType() & DMG_BULLET)
- {
- g_pEffects->Ricochet(ptr->endpos,ptr->plane.normal);
- }
-
- BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
-}
-
-//-----------------------------------------------------------------------------
-// Take damage from being thrown by a physcannon
-//-----------------------------------------------------------------------------
-#define SCANNER_SMASH_SPEED 250.0 // How fast a scanner must slam into something to take full damage
-void CNPC_BaseScanner::TakeDamageFromPhyscannon( CBasePlayer *pPlayer )
-{
- CTakeDamageInfo info;
- info.SetDamageType( DMG_GENERIC );
- info.SetInflictor( this );
- info.SetAttacker( pPlayer );
- info.SetDamagePosition( GetAbsOrigin() );
- info.SetDamageForce( Vector( 1.0, 1.0, 1.0 ) );
-
- // Convert velocity into damage.
- Vector vel;
- VPhysicsGetObject()->GetVelocity( &vel, NULL );
- float flSpeed = vel.Length();
-
- float flFactor = flSpeed / SCANNER_SMASH_SPEED;
-
- // Clamp. Don't inflict negative damage or massive damage!
- flFactor = clamp( flFactor, 0.0f, 2.0f );
- float flDamage = m_iMaxHealth * flFactor;
-
-#if 0
- Msg("Doing %f damage for %f speed!\n", flDamage, flSpeed );
-#endif
-
- info.SetDamage( flDamage );
- TakeDamage( info );
-}
-
-
-//-----------------------------------------------------------------------------
-// Take damage from physics impacts
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::TakeDamageFromPhysicsImpact( int index, gamevcollisionevent_t *pEvent )
-{
- CBaseEntity *pHitEntity = pEvent->pEntities[!index];
-
- // NOTE: Augment the normal impact energy scale here.
- float flDamageScale = PlayerHasMegaPhysCannon() ? 10.0f : 5.0f;
-
- // Scale by the mapmaker's energyscale
- flDamageScale *= m_impactEnergyScale;
-
- int damageType = 0;
- float damage = CalculateDefaultPhysicsDamage( index, pEvent, flDamageScale, true, damageType );
- if ( damage == 0 )
- return;
-
- Vector damagePos;
- pEvent->pInternalData->GetContactPoint( damagePos );
- Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass();
- if ( damageForce == vec3_origin )
- {
- // This can happen if this entity is motion disabled, and can't move.
- // Use the velocity of the entity that hit us instead.
- damageForce = pEvent->postVelocity[!index] * pEvent->pObjects[!index]->GetMass();
- }
-
- // FIXME: this doesn't pass in who is responsible if some other entity "caused" this collision
- PhysCallbackDamage( this, CTakeDamageInfo( pHitEntity, pHitEntity, damageForce, damagePos, damage, damageType ), *pEvent, index );
-}
-
-//-----------------------------------------------------------------------------
-// Is the scanner being held?
-//-----------------------------------------------------------------------------
-bool CNPC_BaseScanner::IsHeldByPhyscannon( )
-{
- return VPhysicsGetObject() && (VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD);
-}
-
-//------------------------------------------------------------------------------
-// Physics impact
-//------------------------------------------------------------------------------
-#define SCANNER_SMASH_TIME 0.75 // How long after being thrown from a physcannon that a manhack is eligible to die from impact
-void CNPC_BaseScanner::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
-{
- BaseClass::VPhysicsCollision( index, pEvent );
-
- // Take no impact damage while being carried.
- if ( IsHeldByPhyscannon( ) )
- return;
-
- CBasePlayer *pPlayer = HasPhysicsAttacker( SCANNER_SMASH_TIME );
- if( pPlayer )
- {
- TakeDamageFromPhyscannon( pPlayer );
- return;
- }
-
- // It also can take physics damage from things thrown by the player.
- int otherIndex = !index;
- CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex];
- if ( pHitEntity )
- {
- if ( pHitEntity->HasPhysicsAttacker( 0.5f ) )
- {
- // It can take physics damage from things thrown by the player.
- TakeDamageFromPhysicsImpact( index, pEvent );
- }
- else if ( FClassnameIs( pHitEntity, "prop_combine_ball" ) )
- {
- // It also can take physics damage from a combine ball.
- TakeDamageFromPhysicsImpact( index, pEvent );
- }
- }
-}
-
-//------------------------------------------------------------------------------
-// Purpose:
-//------------------------------------------------------------------------------
-void CNPC_BaseScanner::Gib( void )
-{
- if ( IsMarkedForDeletion() )
- return;
-
- // Sparks
- for ( int i = 0; i < 4; i++ )
- {
- Vector sparkPos = GetAbsOrigin();
- sparkPos.x += random->RandomFloat(-12,12);
- sparkPos.y += random->RandomFloat(-12,12);
- sparkPos.z += random->RandomFloat(-12,12);
- g_pEffects->Sparks(sparkPos);
- }
-
- // Light
- CBroadcastRecipientFilter filter;
- te->DynamicLight( filter, 0.0, &WorldSpaceCenter(), 255, 180, 100, 0, 100, 0.1, 0 );
-
- // Cover the gib spawn
- ExplosionCreate( WorldSpaceCenter(), GetAbsAngles(), this, 64, 64, false );
-
- // Turn off any smoke trail
- if ( m_pSmokeTrail )
- {
- m_pSmokeTrail->m_ParticleLifetime = 0;
- UTIL_Remove(m_pSmokeTrail);
- m_pSmokeTrail = NULL;
- }
-
- // FIXME: This is because we couldn't save/load the CTakeDamageInfo.
- // because it's midnight before the teamwide playtest. Real solution
- // is to add a datadesc to CTakeDamageInfo
- if ( m_KilledInfo.GetInflictor() )
- {
- BaseClass::Event_Killed( m_KilledInfo );
- }
-
- UTIL_Remove(this);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pPhysGunUser -
-// bPunting -
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
-{
- m_hPhysicsAttacker = pPhysGunUser;
- m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
-
- if ( reason == PUNTED_BY_CANNON )
- {
- // There's about to be a massive change in velocity.
- // Think immediately to handle changes in m_vCurrentVelocity;
- SetNextThink( gpGlobals->curtime + 0.01f );
-
- m_flEngineStallTime = gpGlobals->curtime + 2.0f;
- ScannerEmitSound( "DiveBomb" );
- }
- else
- {
- SetCondition( COND_SCANNER_GRABBED_BY_PHYSCANNON );
- ClearCondition( COND_SCANNER_RELEASED_FROM_PHYSCANNON );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pPhysGunUser -
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
-{
- m_hPhysicsAttacker = pPhysGunUser;
- m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
-
- ClearCondition( COND_SCANNER_GRABBED_BY_PHYSCANNON );
- SetCondition( COND_SCANNER_RELEASED_FROM_PHYSCANNON );
-
- if ( Reason == LAUNCHED_BY_CANNON )
- {
- m_flEngineStallTime = gpGlobals->curtime + 2.0f;
-
- // There's about to be a massive change in velocity.
- // Think immediately to handle changes in m_vCurrentVelocity;
- SetNextThink( gpGlobals->curtime + 0.01f );
- ScannerEmitSound( "DiveBomb" );
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Do we have a physics attacker?
-//------------------------------------------------------------------------------
-CBasePlayer *CNPC_BaseScanner::HasPhysicsAttacker( float dt )
-{
- // If the player is holding me now, or I've been recently thrown
- // then return a pointer to that player
- if ( IsHeldByPhyscannon( ) || (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime) )
- {
- return m_hPhysicsAttacker;
- }
- return NULL;
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose:
-//------------------------------------------------------------------------------
-void CNPC_BaseScanner::StopLoopingSounds(void)
-{
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- controller.SoundDestroy( m_pEngineSound );
- m_pEngineSound = NULL;
-
- BaseClass::StopLoopingSounds();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : pInflictor -
-// pAttacker -
-// flDamage -
-// bitsDamageType -
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::Event_Killed( const CTakeDamageInfo &info )
-{
- // Copy off the takedamage info that killed me, since we're not going to call
- // up into the base class's Event_Killed() until we gib. (gibbing is ultimate death)
- m_KilledInfo = info;
-
- // Interrupt whatever schedule I'm on
- SetCondition(COND_SCHEDULE_DONE);
-
- // If I have an enemy and I'm up high, do a dive bomb (unless dissolved)
- if ( GetEnemy() != NULL && (info.GetDamageType() & DMG_DISSOLVE) == false )
- {
- Vector vecDelta = GetLocalOrigin() - GetEnemy()->GetLocalOrigin();
- if ( ( vecDelta.z > 120 ) && ( vecDelta.Length() > 360 ) )
- {
- // If I'm divebombing, don't take any more damage. It will make Event_Killed() be called again.
- // This is especially bad if someone machineguns the divebombing scanner.
- AttackDivebomb();
- return;
- }
- }
-
- Gib();
-}
-
-//------------------------------------------------------------------------------
-// Purpose:
-//------------------------------------------------------------------------------
-void CNPC_BaseScanner::AttackDivebomb( void )
-{
- ScannerEmitSound( "DiveBomb" );
-
- m_takedamage = DAMAGE_NO;
-
- StartSmokeTrail();
-}
-
-//------------------------------------------------------------------------------
-// Purpose: Checks to see if we hit anything while dive bombing.
-//------------------------------------------------------------------------------
-void CNPC_BaseScanner::AttackDivebombCollide(float flInterval)
-{
- //
- // Trace forward to see if I hit anything
- //
- Vector checkPos = GetAbsOrigin() + (GetCurrentVelocity() * flInterval);
- trace_t tr;
- CBaseEntity* pHitEntity = NULL;
- AI_TraceHull( GetAbsOrigin(), checkPos, GetHullMins(), GetHullMaxs(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
-
- if (tr.m_pEnt)
- {
- pHitEntity = tr.m_pEnt;
-
- // Did I hit an entity that isn't another scanner?
- if (pHitEntity && pHitEntity->Classify()!=CLASS_SCANNER)
- {
- if ( !pHitEntity->ClassMatches("item_battery") )
- {
- if ( !pHitEntity->IsWorld() )
- {
- CTakeDamageInfo info( this, this, sk_scanner_dmg_dive.GetFloat(), DMG_CLUB );
- CalculateMeleeDamageForce( &info, (tr.endpos - tr.startpos), tr.endpos );
- pHitEntity->TakeDamage( info );
- }
- Gib();
- }
- }
- }
-
- if (tr.fraction != 1.0)
- {
- // We've hit something so deflect our velocity based on the surface
- // norm of what we've hit
- if (flInterval > 0)
- {
- float moveLen = (1.0 - tr.fraction)*(GetAbsOrigin() - checkPos).Length();
- Vector vBounceVel = moveLen*tr.plane.normal/flInterval;
-
- // If I'm right over the ground don't push down
- if (vBounceVel.z < 0)
- {
- float floorZ = GetFloorZ(GetAbsOrigin());
- if (abs(GetAbsOrigin().z - floorZ) < 36)
- {
- vBounceVel.z = 0;
- }
- }
- SetCurrentVelocity( GetCurrentVelocity() + vBounceVel );
- }
- CBaseCombatCharacter* pBCC = ToBaseCombatCharacter( pHitEntity );
-
- if (pBCC)
- {
- // Spawn some extra blood where we hit
- SpawnBlood(tr.endpos, g_vecAttackDir, pBCC->BloodColor(), sk_scanner_dmg_dive.GetFloat());
- }
- else
- {
- if (!(m_spawnflags & SF_NPC_GAG))
- {
- // <<TEMP>> need better sound here...
- ScannerEmitSound( "Shoot" );
- }
- // For sparks we must trace a line in the direction of the surface norm
- // that we hit.
- checkPos = GetAbsOrigin() - (tr.plane.normal * 24);
-
- AI_TraceLine( GetAbsOrigin(), checkPos,MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
- if (tr.fraction != 1.0)
- {
- g_pEffects->Sparks( tr.endpos );
-
- CBroadcastRecipientFilter filter;
- te->DynamicLight( filter, 0.0,
- &GetAbsOrigin(), 255, 180, 100, 0, 50, 0.1, 0 );
- }
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::PlayFlySound(void)
-{
- if ( IsMarkedForDeletion() )
- return;
-
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
-
- //Setup the sound if we're not already
- if ( m_pEngineSound == NULL )
- {
- // Create the sound
- CPASAttenuationFilter filter( this );
-
- m_pEngineSound = controller.SoundCreate( filter, entindex(), CHAN_STATIC, GetEngineSound(), ATTN_NORM );
-
- Assert(m_pEngineSound);
-
- // Start the engine sound
- controller.Play( m_pEngineSound, 0.0f, 100.0f );
- controller.SoundChangeVolume( m_pEngineSound, 1.0f, 2.0f );
- }
-
- float speed = GetCurrentVelocity().Length();
- float flVolume = 0.25f + (0.75f*(speed/GetMaxSpeed()));
- int iPitch = MIN( 255, 80 + (20*(speed/GetMaxSpeed())) );
-
- //Update our pitch and volume based on our speed
- controller.SoundChangePitch( m_pEngineSound, iPitch, 0.1f );
- controller.SoundChangeVolume( m_pEngineSound, flVolume, 0.1f );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::ScannerEmitSound( const char *pszSoundName )
-{
- CFmtStr snd;
- snd.sprintf("%s.%s", GetScannerSoundPrefix(), pszSoundName );
-
- m_bHasSpoken = true;
-
- EmitSound( snd.Access() );
-}
-
-//------------------------------------------------------------------------------
-// Purpose:
-//------------------------------------------------------------------------------
-void CNPC_BaseScanner::SpeakSentence( int sentenceType )
-{
- if (sentenceType == SCANNER_SENTENCE_ATTENTION)
- {
- ScannerEmitSound( "Attention" );
- }
- else if (sentenceType == SCANNER_SENTENCE_HANDSUP)
- {
- ScannerEmitSound( "Scan" );
- }
- else if (sentenceType == SCANNER_SENTENCE_PROCEED)
- {
- ScannerEmitSound( "Proceed" );
- }
- else if (sentenceType == SCANNER_SENTENCE_CURIOUS)
- {
- ScannerEmitSound( "Curious" );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::InputSetFlightSpeed(inputdata_t &inputdata)
-{
- //FIXME: Currently unsupported
-
- /*
- m_flFlightSpeed = inputdata.value.Int();
- m_bFlightSpeedOverridden = (m_flFlightSpeed > 0);
- */
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::StartSmokeTrail( void )
-{
- if ( m_pSmokeTrail != NULL )
- return;
-
- m_pSmokeTrail = SmokeTrail::CreateSmokeTrail();
-
- if ( m_pSmokeTrail )
- {
- m_pSmokeTrail->m_SpawnRate = 10;
- m_pSmokeTrail->m_ParticleLifetime = 1;
- m_pSmokeTrail->m_StartSize = 8;
- m_pSmokeTrail->m_EndSize = 50;
- m_pSmokeTrail->m_SpawnRadius = 10;
- m_pSmokeTrail->m_MinSpeed = 15;
- m_pSmokeTrail->m_MaxSpeed = 25;
-
- m_pSmokeTrail->m_StartColor.Init( 0.5f, 0.5f, 0.5f );
- m_pSmokeTrail->m_EndColor.Init( 0, 0, 0 );
- m_pSmokeTrail->SetLifetime( 500.0f );
- m_pSmokeTrail->FollowEntity( this );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::BlendPhyscannonLaunchSpeed()
-{
- // Blend out desired velocity when launched by the physcannon
- if (!VPhysicsGetObject())
- return;
-
- if ( HasPhysicsAttacker( SCANNER_SMASH_TIME ) && !IsHeldByPhyscannon( ) )
- {
- Vector vecCurrentVelocity;
- VPhysicsGetObject()->GetVelocity( &vecCurrentVelocity, NULL );
- float flLerpFactor = (gpGlobals->curtime - m_flLastPhysicsInfluenceTime) / SCANNER_SMASH_TIME;
- flLerpFactor = clamp( flLerpFactor, 0.0f, 1.0f );
- flLerpFactor = SimpleSplineRemapVal( flLerpFactor, 0.0f, 1.0f, 0.0f, 1.0f );
- flLerpFactor *= flLerpFactor;
- VectorLerp( vecCurrentVelocity, m_vCurrentVelocity, flLerpFactor, m_vCurrentVelocity );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::MoveExecute_Alive(float flInterval)
-{
- // Amount of noise to add to flying
- float noiseScale = 3.0f;
-
- // -------------------------------------------
- // Avoid obstacles, unless I'm dive bombing
- // -------------------------------------------
- if (m_nFlyMode != SCANNER_FLY_DIVE)
- {
- SetCurrentVelocity( GetCurrentVelocity() + VelocityToAvoidObstacles(flInterval) );
- }
- // If I am dive bombing add more noise to my flying
- else
- {
- AttackDivebombCollide(flInterval);
- noiseScale *= 4;
- }
-
- IPhysicsObject *pPhysics = VPhysicsGetObject();
-
- if ( pPhysics && pPhysics->IsAsleep() )
- {
- pPhysics->Wake();
- }
-
- // Add time-coherent noise to the current velocity so that it never looks bolted in place.
- AddNoiseToVelocity( noiseScale );
-
- AdjustScannerVelocity();
-
- float maxSpeed = GetEnemy() ? ( GetMaxSpeed() * 2.0f ) : GetMaxSpeed();
- if ( m_nFlyMode == SCANNER_FLY_DIVE )
- {
- maxSpeed = -1;
- }
-
- // Limit fall speed
- LimitSpeed( maxSpeed );
-
- // Blend out desired velocity when launched by the physcannon
- BlendPhyscannonLaunchSpeed();
-
- // Update what we're looking at
- UpdateHead( flInterval );
-
- // Control the tail based on our vertical travel
- float tailPerc = clamp( GetCurrentVelocity().z, -150, 250 );
- tailPerc = SimpleSplineRemapVal( tailPerc, -150, 250, -25, 80 );
-
- SetPoseParameter( m_nPoseTail, tailPerc );
-
- // Spin the dynamo based upon our speed
- float flCurrentDynamo = GetPoseParameter( m_nPoseDynamo );
- float speed = GetCurrentVelocity().Length();
- float flDynamoSpeed = (maxSpeed > 0 ? speed / maxSpeed : 1.0) * 60;
- flCurrentDynamo -= flDynamoSpeed;
- if ( flCurrentDynamo < -180.0 )
- {
- flCurrentDynamo += 360.0;
- }
- SetPoseParameter( m_nPoseDynamo, flCurrentDynamo );
-
- PlayFlySound();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Handles movement towards the last move target.
-// Input : flInterval -
-//-----------------------------------------------------------------------------
-bool CNPC_BaseScanner::OverridePathMove( CBaseEntity *pMoveTarget, float flInterval )
-{
- // Save our last patrolling direction
- Vector lastPatrolDir = GetNavigator()->GetCurWaypointPos() - GetAbsOrigin();
-
- // Continue on our path
- if ( ProgressFlyPath( flInterval, pMoveTarget, (MASK_NPCSOLID|CONTENTS_WATER), false, 64 ) == AINPP_COMPLETE )
- {
- if ( IsCurSchedule( SCHED_SCANNER_PATROL ) )
- {
- m_vLastPatrolDir = lastPatrolDir;
- VectorNormalize(m_vLastPatrolDir);
- }
-
- return true;
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : flInterval -
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CNPC_BaseScanner::OverrideMove( float flInterval )
-{
- // ----------------------------------------------
- // If dive bombing
- // ----------------------------------------------
- if (m_nFlyMode == SCANNER_FLY_DIVE)
- {
- MoveToDivebomb( flInterval );
- }
- else
- {
- Vector vMoveTargetPos(0,0,0);
- CBaseEntity *pMoveTarget = NULL;
-
- // The original line of code was, due to the accidental use of '|' instead of
- // '&', always true. Replacing with 'true' to suppress the warning without changing
- // the (long-standing) behavior.
- if ( true ) //!GetNavigator()->IsGoalActive() || ( GetNavigator()->GetCurWaypointFlags() | bits_WP_TO_PATHCORNER ) )
- {
- // Select move target
- if ( GetTarget() != NULL )
- {
- pMoveTarget = GetTarget();
- }
- else if ( GetEnemy() != NULL )
- {
- pMoveTarget = GetEnemy();
- }
-
- // Select move target position
- if ( GetEnemy() != NULL )
- {
- vMoveTargetPos = GetEnemy()->GetAbsOrigin();
- }
- }
- else
- {
- vMoveTargetPos = GetNavigator()->GetCurWaypointPos();
- }
-
- ClearCondition( COND_SCANNER_FLY_CLEAR );
- ClearCondition( COND_SCANNER_FLY_BLOCKED );
-
- // See if we can fly there directly
- if ( pMoveTarget )
- {
- trace_t tr;
- AI_TraceHull( GetAbsOrigin(), vMoveTargetPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
-
- float fTargetDist = (1.0f-tr.fraction)*(GetAbsOrigin() - vMoveTargetPos).Length();
-
- if ( ( tr.m_pEnt == pMoveTarget ) || ( fTargetDist < 50 ) )
- {
- if ( g_debug_basescanner.GetBool() )
- {
- NDebugOverlay::Line(GetLocalOrigin(), vMoveTargetPos, 0,255,0, true, 0);
- NDebugOverlay::Cross3D(tr.endpos,Vector(-5,-5,-5),Vector(5,5,5),0,255,0,true,0.1);
- }
-
- SetCondition( COND_SCANNER_FLY_CLEAR );
- }
- else
- {
- //HANDY DEBUG TOOL
- if ( g_debug_basescanner.GetBool() )
- {
- NDebugOverlay::Line(GetLocalOrigin(), vMoveTargetPos, 255,0,0, true, 0);
- NDebugOverlay::Cross3D(tr.endpos,Vector(-5,-5,-5),Vector(5,5,5),255,0,0,true,0.1);
- }
-
- SetCondition( COND_SCANNER_FLY_BLOCKED );
- }
- }
-
- // If I have a route, keep it updated and move toward target
- if ( GetNavigator()->IsGoalActive() )
- {
- if ( OverridePathMove( pMoveTarget, flInterval ) )
- {
- BlendPhyscannonLaunchSpeed();
- return true;
- }
- }
- // ----------------------------------------------
- // If attacking
- // ----------------------------------------------
- else if (m_nFlyMode == SCANNER_FLY_ATTACK)
- {
- MoveToAttack( flInterval );
- }
- // -----------------------------------------------------------------
- // If I don't have a route, just decelerate
- // -----------------------------------------------------------------
- else if (!GetNavigator()->IsGoalActive())
- {
- float myDecay = 9.5;
- Decelerate( flInterval, myDecay);
- }
- }
-
- MoveExecute_Alive( flInterval );
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : &goalPos -
-// &startPos -
-// idealRange -
-// idealHeight -
-// Output : Vector
-//-----------------------------------------------------------------------------
-Vector CNPC_BaseScanner::IdealGoalForMovement( const Vector &goalPos, const Vector &startPos, float idealRange, float idealHeightDiff )
-{
- Vector vMoveDir;
-
- if ( GetGoalDirection( &vMoveDir ) == false )
- {
- vMoveDir = ( goalPos - startPos );
- vMoveDir.z = 0;
- VectorNormalize( vMoveDir );
- }
-
- // Move up from the position by the desired amount
- Vector vIdealPos = goalPos + Vector( 0, 0, idealHeightDiff ) + ( vMoveDir * -idealRange );
-
- // Trace down and make sure we can fit here
- trace_t tr;
- AI_TraceHull( vIdealPos, vIdealPos - Vector( 0, 0, MinGroundDist() ), GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
-
- // Move up otherwise
- if ( tr.fraction < 1.0f )
- {
- vIdealPos.z += ( MinGroundDist() * ( 1.0f - tr.fraction ) );
- }
-
- //FIXME: We also need to make sure that we fit here at all, and if not, chose a new spot
-
- // Debug tools
- if ( g_debug_basescanner.GetBool() )
- {
- NDebugOverlay::Cross3D( goalPos, -Vector(8,8,8), Vector(8,8,8), 255, 255, 0, true, 0.1f );
- NDebugOverlay::Cross3D( startPos, -Vector(8,8,8), Vector(8,8,8), 255, 0, 255, true, 0.1f );
- NDebugOverlay::Cross3D( vIdealPos, -Vector(8,8,8), Vector(8,8,8), 255, 255, 255, true, 0.1f );
- NDebugOverlay::Line( startPos, goalPos, 0, 255, 0, true, 0.1f );
-
- NDebugOverlay::Cross3D( goalPos + ( vMoveDir * -idealRange ), -Vector(8,8,8), Vector(8,8,8), 255, 255, 255, true, 0.1f );
- NDebugOverlay::Line( goalPos, goalPos + ( vMoveDir * -idealRange ), 255, 255, 0, true, 0.1f );
- NDebugOverlay::Line( goalPos + ( vMoveDir * -idealRange ), vIdealPos, 255, 255, 0, true, 0.1f );
- }
-
- return vIdealPos;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : flInterval -
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::MoveToAttack(float flInterval)
-{
- if (GetEnemy() == NULL)
- return;
-
- if ( flInterval <= 0 )
- return;
-
- Vector vTargetPos = GetEnemyLKP();
-
- //float flDesiredDist = m_flAttackNearDist + ( ( m_flAttackFarDist - m_flAttackNearDist ) / 2 );
-
- Vector idealPos = IdealGoalForMovement( vTargetPos, GetAbsOrigin(), GetGoalDistance(), m_flAttackNearDist );
-
- MoveToTarget( flInterval, idealPos );
-
- //FIXME: Re-implement?
-
- /*
- // ---------------------------------------------------------
- // Add evasion if I have taken damage recently
- // ---------------------------------------------------------
- if ((m_flLastDamageTime + SCANNER_EVADE_TIME) > gpGlobals->curtime)
- {
- vFlyDirection = vFlyDirection + VelocityToEvade(GetEnemyCombatCharacterPointer());
- }
- */
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Accelerates toward a given position.
-// Input : flInterval - Time interval over which to move.
-// vecMoveTarget - Position to move toward.
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::MoveToTarget( float flInterval, const Vector &vecMoveTarget )
-{
- // Don't move if stalling
- if ( m_flEngineStallTime > gpGlobals->curtime )
- return;
-
- // Look at our inspection target if we have one
- if ( GetEnemy() != NULL )
- {
- // Otherwise at our enemy
- TurnHeadToTarget( flInterval, GetEnemy()->EyePosition() );
- }
- else
- {
- // Otherwise face our motion direction
- TurnHeadToTarget( flInterval, vecMoveTarget );
- }
-
- // -------------------------------------
- // Move towards our target
- // -------------------------------------
- float myAccel;
- float myZAccel = 400.0f;
- float myDecay = 0.15f;
-
- Vector vecCurrentDir;
-
- // Get the relationship between my current velocity and the way I want to be going.
- vecCurrentDir = GetCurrentVelocity();
- VectorNormalize( vecCurrentDir );
-
- Vector targetDir = vecMoveTarget - GetAbsOrigin();
- float flDist = VectorNormalize(targetDir);
-
- float flDot;
- flDot = DotProduct( targetDir, vecCurrentDir );
-
- if( flDot > 0.25 )
- {
- // If my target is in front of me, my flight model is a bit more accurate.
- myAccel = 250;
- }
- else
- {
- // Have a harder time correcting my course if I'm currently flying away from my target.
- myAccel = 128;
- }
-
- if ( myAccel > flDist / flInterval )
- {
- myAccel = flDist / flInterval;
- }
-
- if ( myZAccel > flDist / flInterval )
- {
- myZAccel = flDist / flInterval;
- }
-
- MoveInDirection( flInterval, targetDir, myAccel, myZAccel, myDecay );
-
- // calc relative banking targets
- Vector forward, right, up;
- GetVectors( &forward, &right, &up );
-
- m_vCurrentBanking.x = targetDir.x;
- m_vCurrentBanking.z = 120.0f * DotProduct( right, targetDir );
- m_vCurrentBanking.y = 0;
-
- float speedPerc = SimpleSplineRemapVal( GetCurrentVelocity().Length(), 0.0f, GetMaxSpeed(), 0.0f, 1.0f );
-
- speedPerc = clamp( speedPerc, 0.0f, 1.0f );
-
- m_vCurrentBanking *= speedPerc;
-}
-
-//-----------------------------------------------------------------------------
-// Danger sounds.
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::DiveBombSoundThink()
-{
- Vector vecPosition, vecVelocity;
- IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
-
- if ( pPhysicsObject == NULL )
- return;
-
- pPhysicsObject->GetPosition( &vecPosition, NULL );
- pPhysicsObject->GetVelocity( &vecVelocity, NULL );
-
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
- if ( pPlayer )
- {
- Vector vecDelta;
- VectorSubtract( pPlayer->GetAbsOrigin(), vecPosition, vecDelta );
- VectorNormalize( vecDelta );
- if ( DotProduct( vecDelta, vecVelocity ) > 0.5f )
- {
- Vector vecEndPoint;
- VectorMA( vecPosition, 2.0f * TICK_INTERVAL, vecVelocity, vecEndPoint );
- float flDist = CalcDistanceToLineSegment( pPlayer->GetAbsOrigin(), vecPosition, vecEndPoint );
- if ( flDist < 200.0f )
- {
- ScannerEmitSound( "DiveBombFlyby" );
- SetContextThink( &CNPC_BaseScanner::DiveBombSoundThink, gpGlobals->curtime + 0.5f, s_pDiveBombSoundThinkContext );
- return;
- }
- }
- }
-
- SetContextThink( &CNPC_BaseScanner::DiveBombSoundThink, gpGlobals->curtime + 2.0f * TICK_INTERVAL, s_pDiveBombSoundThinkContext );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : flInterval -
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::MoveToDivebomb(float flInterval)
-{
- float myAccel = 1600;
- float myDecay = 0.05f; // decay current velocity to 10% in 1 second
-
- // Fly towards my enemy
- Vector vEnemyPos = GetEnemyLKP();
- Vector vFlyDirection = vEnemyPos - GetLocalOrigin();
- VectorNormalize( vFlyDirection );
-
- // Set net velocity
- MoveInDirection( flInterval, m_vecDiveBombDirection, myAccel, myAccel, myDecay);
-
- // Spin out of control.
- Vector forward;
- VPhysicsGetObject()->LocalToWorldVector( &forward, Vector( 1.0, 0.0, 0.0 ) );
- AngularImpulse torque = forward * m_flDiveBombRollForce;
- VPhysicsGetObject()->ApplyTorqueCenter( torque );
-
- // BUGBUG: why Y axis and not Z?
- Vector up;
- VPhysicsGetObject()->LocalToWorldVector( &up, Vector( 0.0, 1.0, 0.0 ) );
- VPhysicsGetObject()->ApplyForceCenter( up * 2000 );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool CNPC_BaseScanner::IsEnemyPlayerInSuit()
-{
- if( GetEnemy() && GetEnemy()->IsPlayer() )
- {
- CHL2_Player *pPlayer = NULL;
- pPlayer = (CHL2_Player *)GetEnemy();
-
- if( pPlayer && pPlayer->IsSuitEquipped() )
- {
- return true;
- }
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : float
-//-----------------------------------------------------------------------------
-float CNPC_BaseScanner::GetGoalDistance( void )
-{
- if ( m_flGoalOverrideDistance != 0.0f )
- return m_flGoalOverrideDistance;
-
- switch ( m_nFlyMode )
- {
- case SCANNER_FLY_ATTACK:
- {
- float goalDist = ( m_flAttackNearDist + ( ( m_flAttackFarDist - m_flAttackNearDist ) / 2 ) );
- if( IsEnemyPlayerInSuit() )
- {
- goalDist *= 0.5;
- }
- return goalDist;
- }
- break;
- }
-
- return 128.0f;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : &vOut -
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CNPC_BaseScanner::GetGoalDirection( Vector *vOut )
-{
- CBaseEntity *pTarget = GetTarget();
-
- if ( pTarget == NULL )
- return false;
-
- if ( FClassnameIs( pTarget, "info_hint_air" ) || FClassnameIs( pTarget, "info_target" ) )
- {
- AngleVectors( pTarget->GetAbsAngles(), vOut );
- return true;
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-Vector CNPC_BaseScanner::VelocityToEvade(CBaseCombatCharacter *pEnemy)
-{
- if (pEnemy)
- {
- // -----------------------------------------
- // Keep out of enemy's shooting position
- // -----------------------------------------
- Vector vEnemyFacing = pEnemy->BodyDirection2D( );
- Vector vEnemyDir = pEnemy->EyePosition() - GetLocalOrigin();
- VectorNormalize(vEnemyDir);
- float fDotPr = DotProduct(vEnemyFacing,vEnemyDir);
-
- if (fDotPr < -0.9)
- {
- Vector vDirUp(0,0,1);
- Vector vDir;
- CrossProduct( vEnemyFacing, vDirUp, vDir);
-
- Vector crossProduct;
- CrossProduct(vEnemyFacing, vEnemyDir, crossProduct);
- if (crossProduct.y < 0)
- {
- vDir = vDir * -1;
- }
- return (vDir);
- }
- else if (fDotPr < -0.85)
- {
- Vector vDirUp(0,0,1);
- Vector vDir;
- CrossProduct( vEnemyFacing, vDirUp, vDir);
-
- Vector crossProduct;
- CrossProduct(vEnemyFacing, vEnemyDir, crossProduct);
- if (random->RandomInt(0,1))
- {
- vDir = vDir * -1;
- }
- return (vDir);
- }
- }
- return vec3_origin;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-int CNPC_BaseScanner::DrawDebugTextOverlays(void)
-{
- int nOffset = BaseClass::DrawDebugTextOverlays();
-
- if ( m_debugOverlays & OVERLAY_TEXT_BIT )
- {
- Vector vel;
- GetVelocity( &vel, NULL );
-
- char tempstr[512];
- Q_snprintf( tempstr, sizeof(tempstr), "speed (max): %.2f (%.2f)", vel.Length(), m_flSpeed );
- EntityText( nOffset, tempstr, 0 );
- nOffset++;
- }
-
- return nOffset;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : float
-//-----------------------------------------------------------------------------
-float CNPC_BaseScanner::GetHeadTurnRate( void )
-{
- if ( GetEnemy() )
- return 800.0f;
-
- return 350.0f;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-inline CBaseEntity *CNPC_BaseScanner::EntityToWatch( void )
-{
- return ( GetTarget() != NULL ) ? GetTarget() : GetEnemy(); // Okay if NULL
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : flInterval -
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::UpdateHead( float flInterval )
-{
- float yaw = GetPoseParameter( m_nPoseFaceHoriz );
- float pitch = GetPoseParameter( m_nPoseFaceVert );
-
- CBaseEntity *pTarget = EntityToWatch();
-
- Vector vLookPos;
-
- if ( !HasCondition( COND_IN_PVS ) || GetAttachment( "eyes", vLookPos ) == false )
- {
- vLookPos = EyePosition();
- }
-
- if ( pTarget != NULL )
- {
- Vector lookDir = pTarget->EyePosition() - vLookPos;
- VectorNormalize( lookDir );
-
- if ( DotProduct( lookDir, BodyDirection3D() ) < 0.0f )
- {
- SetPoseParameter( m_nPoseFaceHoriz, UTIL_Approach( 0, yaw, 10 ) );
- SetPoseParameter( m_nPoseFaceVert, UTIL_Approach( 0, pitch, 10 ) );
-
- return;
- }
-
- float facingYaw = VecToYaw( BodyDirection3D() );
- float yawDiff = VecToYaw( lookDir );
- yawDiff = UTIL_AngleDiff( yawDiff, facingYaw + yaw );
-
- float facingPitch = UTIL_VecToPitch( BodyDirection3D() );
- float pitchDiff = UTIL_VecToPitch( lookDir );
- pitchDiff = UTIL_AngleDiff( pitchDiff, facingPitch + pitch );
-
- SetPoseParameter( m_nPoseFaceHoriz, UTIL_Approach( yaw + yawDiff, yaw, 50 ) );
- SetPoseParameter( m_nPoseFaceVert, UTIL_Approach( pitch + pitchDiff, pitch, 50 ) );
- }
- else
- {
- SetPoseParameter( m_nPoseFaceHoriz, UTIL_Approach( 0, yaw, 10 ) );
- SetPoseParameter( m_nPoseFaceVert, UTIL_Approach( 0, pitch, 10 ) );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : &linear -
-// &angular -
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::ClampMotorForces( Vector &linear, AngularImpulse &angular )
-{
- // limit reaction forces
- if ( m_nFlyMode != SCANNER_FLY_DIVE )
- {
- linear.x = clamp( linear.x, -500, 500 );
- linear.y = clamp( linear.y, -500, 500 );
- linear.z = clamp( linear.z, -500, 500 );
- }
-
- // If we're dive bombing, we need to drop faster than normal
- if ( m_nFlyMode != SCANNER_FLY_DIVE )
- {
- // Add in weightlessness
- linear.z += 800;
- }
-
- angular.z = clamp( angular.z, -GetHeadTurnRate(), GetHeadTurnRate() );
- if ( m_nFlyMode == SCANNER_FLY_DIVE )
- {
- // Disable pitch and roll motors while crashing.
- angular.x = 0;
- angular.y = 0;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : &inputdata -
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::InputSetDistanceOverride( inputdata_t &inputdata )
-{
- m_flGoalOverrideDistance = inputdata.value.Float();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Emit sounds specific to the NPC's state.
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::AlertSound(void)
-{
- ScannerEmitSound( "Alert" );
-}
-
-//------------------------------------------------------------------------------
-// Purpose:
-//------------------------------------------------------------------------------
-void CNPC_BaseScanner::DeathSound( const CTakeDamageInfo &info )
-{
- ScannerEmitSound( "Die" );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Overridden so that scanners play battle sounds while fighting.
-// Output : Returns TRUE on success, FALSE on failure.
-//-----------------------------------------------------------------------------
-bool CNPC_BaseScanner::ShouldPlayIdleSound( void )
-{
- if ( HasSpawnFlags( SF_NPC_GAG ) )
- return false;
-
- if ( random->RandomInt( 0, 25 ) != 0 )
- return false;
-
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Plays sounds while idle or in combat.
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::IdleSound(void)
-{
- if ( m_NPCState == NPC_STATE_COMBAT )
- {
- // dvs: the combat sounds should be related to what is happening, rather than random
- ScannerEmitSound( "Combat" );
- }
- else
- {
- ScannerEmitSound( "Idle" );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Plays a sound when hurt.
-//-----------------------------------------------------------------------------
-void CNPC_BaseScanner::PainSound( const CTakeDamageInfo &info )
-{
- ScannerEmitSound( "Pain" );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-float CNPC_BaseScanner::GetMaxSpeed()
-{
- return SCANNER_MAX_SPEED;
-}
-
-//-----------------------------------------------------------------------------
-//
-// Schedules
-//
-//-----------------------------------------------------------------------------
-
-AI_BEGIN_CUSTOM_NPC( npc_basescanner, CNPC_BaseScanner )
-
- DECLARE_TASK( TASK_SCANNER_SET_FLY_PATROL )
- DECLARE_TASK( TASK_SCANNER_SET_FLY_CHASE )
- DECLARE_TASK( TASK_SCANNER_SET_FLY_ATTACK )
- DECLARE_TASK( TASK_SCANNER_SET_FLY_DIVE )
-
- DECLARE_CONDITION(COND_SCANNER_FLY_CLEAR)
- DECLARE_CONDITION(COND_SCANNER_FLY_BLOCKED)
- DECLARE_CONDITION(COND_SCANNER_RELEASED_FROM_PHYSCANNON)
- DECLARE_CONDITION(COND_SCANNER_GRABBED_BY_PHYSCANNON)
-
- //=========================================================
- // > SCHED_SCANNER_PATROL
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_SCANNER_PATROL,
-
- " Tasks"
- " TASK_SCANNER_SET_FLY_PATROL 0"
- " TASK_SET_TOLERANCE_DISTANCE 32"
- " TASK_SET_ROUTE_SEARCH_TIME 5" // Spend 5 seconds trying to build a path if stuck
- " TASK_GET_PATH_TO_RANDOM_NODE 2000"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- ""
- " Interrupts"
- " COND_GIVE_WAY"
- " COND_NEW_ENEMY"
- " COND_SEE_ENEMY"
- " COND_SEE_FEAR"
- " COND_HEAR_COMBAT"
- " COND_HEAR_DANGER"
- " COND_HEAR_PLAYER"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- " COND_PROVOKED"
- " COND_SCANNER_GRABBED_BY_PHYSCANNON"
- )
-
- //=========================================================
- // > SCHED_SCANNER_ATTACK
- //
- // This task does nothing. Translate it in your derived
- // class to perform your attack.
- //
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_SCANNER_ATTACK,
-
- " Tasks"
- " TASK_SCANNER_SET_FLY_ATTACK 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_WAIT 0.1"
- ""
- " Interrupts"
- " COND_TOO_FAR_TO_ATTACK"
- " COND_SCANNER_FLY_BLOCKED"
- " COND_NEW_ENEMY"
- " COND_SCANNER_GRABBED_BY_PHYSCANNON"
- )
-
- //=========================================================
- // > SCHED_SCANNER_ATTACK_HOVER
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_SCANNER_ATTACK_HOVER,
-
- " Tasks"
- " TASK_SCANNER_SET_FLY_ATTACK 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_WAIT 0.1"
- ""
- " Interrupts"
- " COND_TOO_FAR_TO_ATTACK"
- " COND_SCANNER_FLY_BLOCKED"
- " COND_NEW_ENEMY"
- " COND_SCANNER_GRABBED_BY_PHYSCANNON"
- )
-
- //=========================================================
- // > SCHED_SCANNER_ATTACK_DIVEBOMB
- //
- // Only done when scanner is dead
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_SCANNER_ATTACK_DIVEBOMB,
-
- " Tasks"
- " TASK_SCANNER_SET_FLY_DIVE 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_WAIT 10"
- ""
- " Interrupts"
- " COND_SCANNER_GRABBED_BY_PHYSCANNON"
- )
-
- //=========================================================
- // > SCHED_SCANNER_CHASE_ENEMY
- //
- // Different interrupts than normal chase enemy.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_SCANNER_CHASE_ENEMY,
-
- " Tasks"
- " TASK_SCANNER_SET_FLY_CHASE 0"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCANNER_PATROL"
- " TASK_SET_TOLERANCE_DISTANCE 120"
- " TASK_GET_PATH_TO_ENEMY 0"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- ""
- ""
- " Interrupts"
- " COND_SCANNER_FLY_CLEAR"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_LOST_ENEMY"
- " COND_SCANNER_GRABBED_BY_PHYSCANNON"
- )
-
- //=========================================================
- // > SCHED_SCANNER_CHASE_TARGET
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_SCANNER_CHASE_TARGET,
-
- " Tasks"
- " TASK_SCANNER_SET_FLY_CHASE 0"
- " TASK_SET_TOLERANCE_DISTANCE 64"
- " TASK_GET_PATH_TO_TARGET 0" //FIXME: This is wrong!
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- ""
- " Interrupts"
- " COND_SCANNER_FLY_CLEAR"
- " COND_NEW_ENEMY"
- " COND_SCANNER_GRABBED_BY_PHYSCANNON"
- )
-
- //=========================================================
- // > SCHED_SCANNER_FOLLOW_HOVER
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_SCANNER_FOLLOW_HOVER,
-
- " Tasks"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_WAIT 0.1"
- ""
- " Interrupts"
- " COND_SCANNER_FLY_BLOCKED"
- " COND_SCANNER_GRABBED_BY_PHYSCANNON"
- )
-
- //=========================================================
- // > SCHED_SCANNER_HELD_BY_PHYSCANNON
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_SCANNER_HELD_BY_PHYSCANNON,
-
- " Tasks"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_WAIT 5.0"
- ""
- " Interrupts"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- " COND_SCANNER_RELEASED_FROM_PHYSCANNON"
- )
-
-AI_END_CUSTOM_NPC()
+//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "npcevent.h" +#include "ai_basenpc_physicsflyer.h" +#include "weapon_physcannon.h" +#include "hl2_player.h" +#include "npc_scanner.h" +#include "IEffects.h" +#include "explode.h" +#include "ai_route.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +ConVar g_debug_basescanner( "g_debug_basescanner", "0", FCVAR_CHEAT ); + +BEGIN_DATADESC( CNPC_BaseScanner ) + DEFINE_EMBEDDED( m_KilledInfo ), + DEFINE_SOUNDPATCH( m_pEngineSound ), + + DEFINE_FIELD( m_flFlyNoiseBase, FIELD_FLOAT ), + DEFINE_FIELD( m_flEngineStallTime, FIELD_TIME ), + DEFINE_FIELD( m_fNextFlySoundTime, FIELD_TIME ), + DEFINE_FIELD( m_nFlyMode, FIELD_INTEGER ), + + DEFINE_FIELD( m_vecDiveBombDirection, FIELD_VECTOR ), + DEFINE_FIELD( m_flDiveBombRollForce, FIELD_FLOAT ), + + // Physics Influence + DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ), + DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ), + + DEFINE_FIELD( m_flGoalOverrideDistance, FIELD_FLOAT ), + + DEFINE_FIELD( m_flAttackNearDist, FIELD_FLOAT ), + DEFINE_FIELD( m_flAttackFarDist, FIELD_FLOAT ), + DEFINE_FIELD( m_flAttackRange, FIELD_FLOAT ), + + DEFINE_FIELD( m_nPoseTail, FIELD_INTEGER ), + DEFINE_FIELD( m_nPoseDynamo, FIELD_INTEGER ), + DEFINE_FIELD( m_nPoseFlare, FIELD_INTEGER ), + DEFINE_FIELD( m_nPoseFaceVert, FIELD_INTEGER ), + DEFINE_FIELD( m_nPoseFaceHoriz, FIELD_INTEGER ), + + // DEFINE_FIELD( m_bHasSpoken, FIELD_BOOLEAN ), + + DEFINE_FIELD( m_pSmokeTrail, FIELD_CLASSPTR ), + + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDistanceOverride", InputSetDistanceOverride ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetFlightSpeed", InputSetFlightSpeed ), + + DEFINE_THINKFUNC( DiveBombSoundThink ), +END_DATADESC() + +ConVar sk_scanner_dmg_dive( "sk_scanner_dmg_dive","0"); + +//----------------------------------------------------------------------------- +// Think contexts +//----------------------------------------------------------------------------- +static const char *s_pDiveBombSoundThinkContext = "DiveBombSoundThinkContext"; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CNPC_BaseScanner::CNPC_BaseScanner() +{ +#ifdef _DEBUG + m_vCurrentBanking.Init(); +#endif + m_pEngineSound = NULL; + m_bHasSpoken = false; + + m_flAttackNearDist = SCANNER_ATTACK_NEAR_DIST; + m_flAttackFarDist = SCANNER_ATTACK_FAR_DIST; + m_flAttackRange = SCANNER_ATTACK_RANGE; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::Spawn(void) +{ +#ifdef _XBOX + // Always fade the corpse + AddSpawnFlags( SF_NPC_FADE_CORPSE ); + AddEffects( EF_NOSHADOW ); +#endif // _XBOX + + SetHullType( HULL_TINY_CENTERED ); + SetHullSizeNormal(); + + SetSolid( SOLID_BBOX ); + AddSolidFlags( FSOLID_NOT_STANDABLE ); + + SetMoveType( MOVETYPE_VPHYSICS ); + + m_bloodColor = DONT_BLEED; + SetViewOffset( Vector(0, 0, 10) ); // Position of the eyes relative to NPC's origin. + m_flFieldOfView = 0.2; + m_NPCState = NPC_STATE_NONE; + + SetNavType( NAV_FLY ); + + AddFlag( FL_FLY ); + + // This entity cannot be dissolved by the combine balls, + // nor does it get killed by the mega physcannon. + AddEFlags( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL ); + + m_flGoalOverrideDistance = 0.0f; + + m_nFlyMode = SCANNER_FLY_PATROL; + AngleVectors( GetLocalAngles(), &m_vCurrentBanking ); + m_fHeadYaw = 0; + m_pSmokeTrail = NULL; + + SetCurrentVelocity( vec3_origin ); + + // Noise modifier + Vector bobAmount; + bobAmount.x = random->RandomFloat( -2.0f, 2.0f ); + bobAmount.y = random->RandomFloat( -2.0f, 2.0f ); + bobAmount.z = random->RandomFloat( 2.0f, 4.0f ); + if ( random->RandomInt( 0, 1 ) ) + { + bobAmount.z *= -1.0f; + } + SetNoiseMod( bobAmount ); + + // set flight speed + m_flSpeed = GetMaxSpeed(); + + // -------------------------------------------- + + CapabilitiesAdd( bits_CAP_MOVE_FLY | bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_SKIP_NAV_GROUND_CHECK ); + + NPCInit(); + + m_flFlyNoiseBase = random->RandomFloat( 0, M_PI ); + + m_flNextAttack = gpGlobals->curtime; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::UpdateEfficiency( bool bInPVS ) +{ + SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL ); + SetMoveEfficiency( AIME_NORMAL ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +float CNPC_BaseScanner::GetAutoAimRadius() +{ + if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE ) + { + return 24.0f; + } + + return 12.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Called just before we are deleted. +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::UpdateOnRemove( void ) +{ + // Stop combat loops if I'm alive. If I'm dead, the die sound will already have stopped it. + if ( IsAlive() && m_bHasSpoken ) + { + SentenceStop(); + } + + BaseClass::UpdateOnRemove(); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets the appropriate next schedule based on current condition +// bits. +//----------------------------------------------------------------------------- +int CNPC_BaseScanner::SelectSchedule(void) +{ + // ---------------------------------------------------- + // If I'm dead, go into a dive bomb + // ---------------------------------------------------- + if ( m_iHealth <= 0 ) + { + m_flSpeed = SCANNER_MAX_DIVE_BOMB_SPEED; + return SCHED_SCANNER_ATTACK_DIVEBOMB; + } + + // ------------------------------- + // If I'm in a script sequence + // ------------------------------- + if ( m_NPCState == NPC_STATE_SCRIPT ) + return(BaseClass::SelectSchedule()); + + // ------------------------------- + // Flinch + // ------------------------------- + if ( HasCondition(COND_LIGHT_DAMAGE) || HasCondition(COND_HEAVY_DAMAGE) ) + { + if ( IsHeldByPhyscannon( ) ) + return SCHED_SMALL_FLINCH; + + if ( m_NPCState == NPC_STATE_IDLE ) + return SCHED_SMALL_FLINCH; + + if ( m_NPCState == NPC_STATE_ALERT ) + { + if ( m_iHealth < ( 3 * m_iMaxHealth / 4 )) + return SCHED_TAKE_COVER_FROM_ORIGIN; + + if ( SelectWeightedSequence( ACT_SMALL_FLINCH ) != -1 ) + return SCHED_SMALL_FLINCH; + } + else + { + if ( random->RandomInt( 0, 10 ) < 4 ) + return SCHED_SMALL_FLINCH; + } + } + + // I'm being held by the physcannon... struggle! + if ( IsHeldByPhyscannon( ) ) + return SCHED_SCANNER_HELD_BY_PHYSCANNON; + + // ---------------------------------------------------------- + // If I have an enemy + // ---------------------------------------------------------- + if ( GetEnemy() != NULL && GetEnemy()->IsAlive() ) + { + // Patrol if the enemy has vanished + if ( HasCondition( COND_LOST_ENEMY ) ) + return SCHED_SCANNER_PATROL; + + // Chase via route if we're directly blocked + if ( HasCondition( COND_SCANNER_FLY_BLOCKED ) ) + return SCHED_SCANNER_CHASE_ENEMY; + + // Attack if it's time + if ( gpGlobals->curtime >= m_flNextAttack ) + { + if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) ) + return SCHED_SCANNER_ATTACK; + } + + // Otherwise fly in low for attack + return SCHED_SCANNER_ATTACK_HOVER; + } + + // Default to patrolling around + return SCHED_SCANNER_PATROL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::OnScheduleChange( void ) +{ + m_flSpeed = GetMaxSpeed(); + + BaseClass::OnScheduleChange(); +} + +//----------------------------------------------------------------------------- +// Purpose: For innate melee attack +//----------------------------------------------------------------------------- +int CNPC_BaseScanner::MeleeAttack1Conditions( float flDot, float flDist ) +{ + if (GetEnemy() == NULL) + { + return COND_NONE; + } + + // Check too far to attack with 2D distance + float vEnemyDist2D = (GetEnemy()->GetLocalOrigin() - GetLocalOrigin()).Length2D(); + + if (m_flNextAttack > gpGlobals->curtime) + { + return COND_NONE; + } + else if (vEnemyDist2D > m_flAttackRange) + { + return COND_TOO_FAR_TO_ATTACK; + } + else if (flDot < 0.7) + { + return COND_NOT_FACING_ATTACK; + } + return COND_CAN_MELEE_ATTACK1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : eOldState - +// eNewState - +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::OnStateChange( NPC_STATE eOldState, NPC_STATE eNewState ) +{ + if (( eNewState == NPC_STATE_ALERT ) || ( eNewState == NPC_STATE_COMBAT )) + { + SetPoseParameter(m_nPoseFlare, 1.0f); + } + else + { + SetPoseParameter(m_nPoseFlare, 0); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pTask - +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::StartTask( const Task_t *pTask ) +{ + switch (pTask->iTask) + { + case TASK_SCANNER_SET_FLY_PATROL: + { + // Fly in patrol mode and clear any + // remaining target entity + m_nFlyMode = SCANNER_FLY_PATROL; + TaskComplete(); + break; + } + case TASK_SCANNER_SET_FLY_CHASE: + { + m_nFlyMode = SCANNER_FLY_CHASE; + TaskComplete(); + break; + } + case TASK_SCANNER_SET_FLY_ATTACK: + { + m_nFlyMode = SCANNER_FLY_ATTACK; + TaskComplete(); + break; + } + + case TASK_SCANNER_SET_FLY_DIVE: + { + // Pick a direction to divebomb. + if ( GetEnemy() != NULL ) + { + // Fly towards my enemy + Vector vEnemyPos = GetEnemyLKP(); + m_vecDiveBombDirection = vEnemyPos - GetLocalOrigin(); + } + else + { + // Pick a random forward and down direction. + Vector forward; + GetVectors( &forward, NULL, NULL ); + m_vecDiveBombDirection = forward + Vector( random->RandomFloat( -10, 10 ), random->RandomFloat( -10, 10 ), random->RandomFloat( -20, -10 ) ); + } + VectorNormalize( m_vecDiveBombDirection ); + + // Calculate a roll force. + m_flDiveBombRollForce = random->RandomFloat( 20.0, 420.0 ); + if ( random->RandomInt( 0, 1 ) ) + { + m_flDiveBombRollForce *= -1; + } + + DiveBombSoundThink(); + + m_nFlyMode = SCANNER_FLY_DIVE; + TaskComplete(); + break; + } + + default: + BaseClass::StartTask(pTask); + break; + } +} + +//------------------------------------------------------------------------------ +// Purpose: Override to split in two when attacked +//------------------------------------------------------------------------------ +int CNPC_BaseScanner::OnTakeDamage_Alive( const CTakeDamageInfo &info ) +{ + // Start smoking when we're nearly dead + if ( m_iHealth < ( m_iMaxHealth - ( m_iMaxHealth / 4 ) ) ) + { + StartSmokeTrail(); + } + + return (BaseClass::OnTakeDamage_Alive( info )); +} + +//------------------------------------------------------------------------------ +// Purpose: Override to split in two when attacked +//------------------------------------------------------------------------------ +int CNPC_BaseScanner::OnTakeDamage_Dying( const CTakeDamageInfo &info ) +{ + // do the damage + m_iHealth -= info.GetDamage(); + + if ( m_iHealth < -40 ) + { + Gib(); + return 1; + } + + return VPhysicsTakeDamage( info ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) +{ + if ( info.GetDamageType() & DMG_BULLET) + { + g_pEffects->Ricochet(ptr->endpos,ptr->plane.normal); + } + + BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator ); +} + +//----------------------------------------------------------------------------- +// Take damage from being thrown by a physcannon +//----------------------------------------------------------------------------- +#define SCANNER_SMASH_SPEED 250.0 // How fast a scanner must slam into something to take full damage +void CNPC_BaseScanner::TakeDamageFromPhyscannon( CBasePlayer *pPlayer ) +{ + CTakeDamageInfo info; + info.SetDamageType( DMG_GENERIC ); + info.SetInflictor( this ); + info.SetAttacker( pPlayer ); + info.SetDamagePosition( GetAbsOrigin() ); + info.SetDamageForce( Vector( 1.0, 1.0, 1.0 ) ); + + // Convert velocity into damage. + Vector vel; + VPhysicsGetObject()->GetVelocity( &vel, NULL ); + float flSpeed = vel.Length(); + + float flFactor = flSpeed / SCANNER_SMASH_SPEED; + + // Clamp. Don't inflict negative damage or massive damage! + flFactor = clamp( flFactor, 0.0f, 2.0f ); + float flDamage = m_iMaxHealth * flFactor; + +#if 0 + Msg("Doing %f damage for %f speed!\n", flDamage, flSpeed ); +#endif + + info.SetDamage( flDamage ); + TakeDamage( info ); +} + + +//----------------------------------------------------------------------------- +// Take damage from physics impacts +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::TakeDamageFromPhysicsImpact( int index, gamevcollisionevent_t *pEvent ) +{ + CBaseEntity *pHitEntity = pEvent->pEntities[!index]; + + // NOTE: Augment the normal impact energy scale here. + float flDamageScale = PlayerHasMegaPhysCannon() ? 10.0f : 5.0f; + + // Scale by the mapmaker's energyscale + flDamageScale *= m_impactEnergyScale; + + int damageType = 0; + float damage = CalculateDefaultPhysicsDamage( index, pEvent, flDamageScale, true, damageType ); + if ( damage == 0 ) + return; + + Vector damagePos; + pEvent->pInternalData->GetContactPoint( damagePos ); + Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass(); + if ( damageForce == vec3_origin ) + { + // This can happen if this entity is motion disabled, and can't move. + // Use the velocity of the entity that hit us instead. + damageForce = pEvent->postVelocity[!index] * pEvent->pObjects[!index]->GetMass(); + } + + // FIXME: this doesn't pass in who is responsible if some other entity "caused" this collision + PhysCallbackDamage( this, CTakeDamageInfo( pHitEntity, pHitEntity, damageForce, damagePos, damage, damageType ), *pEvent, index ); +} + +//----------------------------------------------------------------------------- +// Is the scanner being held? +//----------------------------------------------------------------------------- +bool CNPC_BaseScanner::IsHeldByPhyscannon( ) +{ + return VPhysicsGetObject() && (VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD); +} + +//------------------------------------------------------------------------------ +// Physics impact +//------------------------------------------------------------------------------ +#define SCANNER_SMASH_TIME 0.75 // How long after being thrown from a physcannon that a manhack is eligible to die from impact +void CNPC_BaseScanner::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) +{ + BaseClass::VPhysicsCollision( index, pEvent ); + + // Take no impact damage while being carried. + if ( IsHeldByPhyscannon( ) ) + return; + + CBasePlayer *pPlayer = HasPhysicsAttacker( SCANNER_SMASH_TIME ); + if( pPlayer ) + { + TakeDamageFromPhyscannon( pPlayer ); + return; + } + + // It also can take physics damage from things thrown by the player. + int otherIndex = !index; + CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex]; + if ( pHitEntity ) + { + if ( pHitEntity->HasPhysicsAttacker( 0.5f ) ) + { + // It can take physics damage from things thrown by the player. + TakeDamageFromPhysicsImpact( index, pEvent ); + } + else if ( FClassnameIs( pHitEntity, "prop_combine_ball" ) ) + { + // It also can take physics damage from a combine ball. + TakeDamageFromPhysicsImpact( index, pEvent ); + } + } +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CNPC_BaseScanner::Gib( void ) +{ + if ( IsMarkedForDeletion() ) + return; + + // Sparks + for ( int i = 0; i < 4; i++ ) + { + Vector sparkPos = GetAbsOrigin(); + sparkPos.x += random->RandomFloat(-12,12); + sparkPos.y += random->RandomFloat(-12,12); + sparkPos.z += random->RandomFloat(-12,12); + g_pEffects->Sparks(sparkPos); + } + + // Light + CBroadcastRecipientFilter filter; + te->DynamicLight( filter, 0.0, &WorldSpaceCenter(), 255, 180, 100, 0, 100, 0.1, 0 ); + + // Cover the gib spawn + ExplosionCreate( WorldSpaceCenter(), GetAbsAngles(), this, 64, 64, false ); + + // Turn off any smoke trail + if ( m_pSmokeTrail ) + { + m_pSmokeTrail->m_ParticleLifetime = 0; + UTIL_Remove(m_pSmokeTrail); + m_pSmokeTrail = NULL; + } + + // FIXME: This is because we couldn't save/load the CTakeDamageInfo. + // because it's midnight before the teamwide playtest. Real solution + // is to add a datadesc to CTakeDamageInfo + if ( m_KilledInfo.GetInflictor() ) + { + BaseClass::Event_Killed( m_KilledInfo ); + } + + UTIL_Remove(this); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPhysGunUser - +// bPunting - +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason ) +{ + m_hPhysicsAttacker = pPhysGunUser; + m_flLastPhysicsInfluenceTime = gpGlobals->curtime; + + if ( reason == PUNTED_BY_CANNON ) + { + // There's about to be a massive change in velocity. + // Think immediately to handle changes in m_vCurrentVelocity; + SetNextThink( gpGlobals->curtime + 0.01f ); + + m_flEngineStallTime = gpGlobals->curtime + 2.0f; + ScannerEmitSound( "DiveBomb" ); + } + else + { + SetCondition( COND_SCANNER_GRABBED_BY_PHYSCANNON ); + ClearCondition( COND_SCANNER_RELEASED_FROM_PHYSCANNON ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPhysGunUser - +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason ) +{ + m_hPhysicsAttacker = pPhysGunUser; + m_flLastPhysicsInfluenceTime = gpGlobals->curtime; + + ClearCondition( COND_SCANNER_GRABBED_BY_PHYSCANNON ); + SetCondition( COND_SCANNER_RELEASED_FROM_PHYSCANNON ); + + if ( Reason == LAUNCHED_BY_CANNON ) + { + m_flEngineStallTime = gpGlobals->curtime + 2.0f; + + // There's about to be a massive change in velocity. + // Think immediately to handle changes in m_vCurrentVelocity; + SetNextThink( gpGlobals->curtime + 0.01f ); + ScannerEmitSound( "DiveBomb" ); + } +} + + +//------------------------------------------------------------------------------ +// Do we have a physics attacker? +//------------------------------------------------------------------------------ +CBasePlayer *CNPC_BaseScanner::HasPhysicsAttacker( float dt ) +{ + // If the player is holding me now, or I've been recently thrown + // then return a pointer to that player + if ( IsHeldByPhyscannon( ) || (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime) ) + { + return m_hPhysicsAttacker; + } + return NULL; +} + + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CNPC_BaseScanner::StopLoopingSounds(void) +{ + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + controller.SoundDestroy( m_pEngineSound ); + m_pEngineSound = NULL; + + BaseClass::StopLoopingSounds(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pInflictor - +// pAttacker - +// flDamage - +// bitsDamageType - +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::Event_Killed( const CTakeDamageInfo &info ) +{ + // Copy off the takedamage info that killed me, since we're not going to call + // up into the base class's Event_Killed() until we gib. (gibbing is ultimate death) + m_KilledInfo = info; + + // Interrupt whatever schedule I'm on + SetCondition(COND_SCHEDULE_DONE); + + // If I have an enemy and I'm up high, do a dive bomb (unless dissolved) + if ( GetEnemy() != NULL && (info.GetDamageType() & DMG_DISSOLVE) == false ) + { + Vector vecDelta = GetLocalOrigin() - GetEnemy()->GetLocalOrigin(); + if ( ( vecDelta.z > 120 ) && ( vecDelta.Length() > 360 ) ) + { + // If I'm divebombing, don't take any more damage. It will make Event_Killed() be called again. + // This is especially bad if someone machineguns the divebombing scanner. + AttackDivebomb(); + return; + } + } + + Gib(); +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CNPC_BaseScanner::AttackDivebomb( void ) +{ + ScannerEmitSound( "DiveBomb" ); + + m_takedamage = DAMAGE_NO; + + StartSmokeTrail(); +} + +//------------------------------------------------------------------------------ +// Purpose: Checks to see if we hit anything while dive bombing. +//------------------------------------------------------------------------------ +void CNPC_BaseScanner::AttackDivebombCollide(float flInterval) +{ + // + // Trace forward to see if I hit anything + // + Vector checkPos = GetAbsOrigin() + (GetCurrentVelocity() * flInterval); + trace_t tr; + CBaseEntity* pHitEntity = NULL; + AI_TraceHull( GetAbsOrigin(), checkPos, GetHullMins(), GetHullMaxs(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); + + if (tr.m_pEnt) + { + pHitEntity = tr.m_pEnt; + + // Did I hit an entity that isn't another scanner? + if (pHitEntity && pHitEntity->Classify()!=CLASS_SCANNER) + { + if ( !pHitEntity->ClassMatches("item_battery") ) + { + if ( !pHitEntity->IsWorld() ) + { + CTakeDamageInfo info( this, this, sk_scanner_dmg_dive.GetFloat(), DMG_CLUB ); + CalculateMeleeDamageForce( &info, (tr.endpos - tr.startpos), tr.endpos ); + pHitEntity->TakeDamage( info ); + } + Gib(); + } + } + } + + if (tr.fraction != 1.0) + { + // We've hit something so deflect our velocity based on the surface + // norm of what we've hit + if (flInterval > 0) + { + float moveLen = (1.0 - tr.fraction)*(GetAbsOrigin() - checkPos).Length(); + Vector vBounceVel = moveLen*tr.plane.normal/flInterval; + + // If I'm right over the ground don't push down + if (vBounceVel.z < 0) + { + float floorZ = GetFloorZ(GetAbsOrigin()); + if (abs(GetAbsOrigin().z - floorZ) < 36) + { + vBounceVel.z = 0; + } + } + SetCurrentVelocity( GetCurrentVelocity() + vBounceVel ); + } + CBaseCombatCharacter* pBCC = ToBaseCombatCharacter( pHitEntity ); + + if (pBCC) + { + // Spawn some extra blood where we hit + SpawnBlood(tr.endpos, g_vecAttackDir, pBCC->BloodColor(), sk_scanner_dmg_dive.GetFloat()); + } + else + { + if (!(m_spawnflags & SF_NPC_GAG)) + { + // <<TEMP>> need better sound here... + ScannerEmitSound( "Shoot" ); + } + // For sparks we must trace a line in the direction of the surface norm + // that we hit. + checkPos = GetAbsOrigin() - (tr.plane.normal * 24); + + AI_TraceLine( GetAbsOrigin(), checkPos,MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); + if (tr.fraction != 1.0) + { + g_pEffects->Sparks( tr.endpos ); + + CBroadcastRecipientFilter filter; + te->DynamicLight( filter, 0.0, + &GetAbsOrigin(), 255, 180, 100, 0, 50, 0.1, 0 ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::PlayFlySound(void) +{ + if ( IsMarkedForDeletion() ) + return; + + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + + //Setup the sound if we're not already + if ( m_pEngineSound == NULL ) + { + // Create the sound + CPASAttenuationFilter filter( this ); + + m_pEngineSound = controller.SoundCreate( filter, entindex(), CHAN_STATIC, GetEngineSound(), ATTN_NORM ); + + Assert(m_pEngineSound); + + // Start the engine sound + controller.Play( m_pEngineSound, 0.0f, 100.0f ); + controller.SoundChangeVolume( m_pEngineSound, 1.0f, 2.0f ); + } + + float speed = GetCurrentVelocity().Length(); + float flVolume = 0.25f + (0.75f*(speed/GetMaxSpeed())); + int iPitch = MIN( 255, 80 + (20*(speed/GetMaxSpeed())) ); + + //Update our pitch and volume based on our speed + controller.SoundChangePitch( m_pEngineSound, iPitch, 0.1f ); + controller.SoundChangeVolume( m_pEngineSound, flVolume, 0.1f ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::ScannerEmitSound( const char *pszSoundName ) +{ + CFmtStr snd; + snd.sprintf("%s.%s", GetScannerSoundPrefix(), pszSoundName ); + + m_bHasSpoken = true; + + EmitSound( snd.Access() ); +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CNPC_BaseScanner::SpeakSentence( int sentenceType ) +{ + if (sentenceType == SCANNER_SENTENCE_ATTENTION) + { + ScannerEmitSound( "Attention" ); + } + else if (sentenceType == SCANNER_SENTENCE_HANDSUP) + { + ScannerEmitSound( "Scan" ); + } + else if (sentenceType == SCANNER_SENTENCE_PROCEED) + { + ScannerEmitSound( "Proceed" ); + } + else if (sentenceType == SCANNER_SENTENCE_CURIOUS) + { + ScannerEmitSound( "Curious" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::InputSetFlightSpeed(inputdata_t &inputdata) +{ + //FIXME: Currently unsupported + + /* + m_flFlightSpeed = inputdata.value.Int(); + m_bFlightSpeedOverridden = (m_flFlightSpeed > 0); + */ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::StartSmokeTrail( void ) +{ + if ( m_pSmokeTrail != NULL ) + return; + + m_pSmokeTrail = SmokeTrail::CreateSmokeTrail(); + + if ( m_pSmokeTrail ) + { + m_pSmokeTrail->m_SpawnRate = 10; + m_pSmokeTrail->m_ParticleLifetime = 1; + m_pSmokeTrail->m_StartSize = 8; + m_pSmokeTrail->m_EndSize = 50; + m_pSmokeTrail->m_SpawnRadius = 10; + m_pSmokeTrail->m_MinSpeed = 15; + m_pSmokeTrail->m_MaxSpeed = 25; + + m_pSmokeTrail->m_StartColor.Init( 0.5f, 0.5f, 0.5f ); + m_pSmokeTrail->m_EndColor.Init( 0, 0, 0 ); + m_pSmokeTrail->SetLifetime( 500.0f ); + m_pSmokeTrail->FollowEntity( this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::BlendPhyscannonLaunchSpeed() +{ + // Blend out desired velocity when launched by the physcannon + if (!VPhysicsGetObject()) + return; + + if ( HasPhysicsAttacker( SCANNER_SMASH_TIME ) && !IsHeldByPhyscannon( ) ) + { + Vector vecCurrentVelocity; + VPhysicsGetObject()->GetVelocity( &vecCurrentVelocity, NULL ); + float flLerpFactor = (gpGlobals->curtime - m_flLastPhysicsInfluenceTime) / SCANNER_SMASH_TIME; + flLerpFactor = clamp( flLerpFactor, 0.0f, 1.0f ); + flLerpFactor = SimpleSplineRemapVal( flLerpFactor, 0.0f, 1.0f, 0.0f, 1.0f ); + flLerpFactor *= flLerpFactor; + VectorLerp( vecCurrentVelocity, m_vCurrentVelocity, flLerpFactor, m_vCurrentVelocity ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::MoveExecute_Alive(float flInterval) +{ + // Amount of noise to add to flying + float noiseScale = 3.0f; + + // ------------------------------------------- + // Avoid obstacles, unless I'm dive bombing + // ------------------------------------------- + if (m_nFlyMode != SCANNER_FLY_DIVE) + { + SetCurrentVelocity( GetCurrentVelocity() + VelocityToAvoidObstacles(flInterval) ); + } + // If I am dive bombing add more noise to my flying + else + { + AttackDivebombCollide(flInterval); + noiseScale *= 4; + } + + IPhysicsObject *pPhysics = VPhysicsGetObject(); + + if ( pPhysics && pPhysics->IsAsleep() ) + { + pPhysics->Wake(); + } + + // Add time-coherent noise to the current velocity so that it never looks bolted in place. + AddNoiseToVelocity( noiseScale ); + + AdjustScannerVelocity(); + + float maxSpeed = GetEnemy() ? ( GetMaxSpeed() * 2.0f ) : GetMaxSpeed(); + if ( m_nFlyMode == SCANNER_FLY_DIVE ) + { + maxSpeed = -1; + } + + // Limit fall speed + LimitSpeed( maxSpeed ); + + // Blend out desired velocity when launched by the physcannon + BlendPhyscannonLaunchSpeed(); + + // Update what we're looking at + UpdateHead( flInterval ); + + // Control the tail based on our vertical travel + float tailPerc = clamp( GetCurrentVelocity().z, -150, 250 ); + tailPerc = SimpleSplineRemapVal( tailPerc, -150, 250, -25, 80 ); + + SetPoseParameter( m_nPoseTail, tailPerc ); + + // Spin the dynamo based upon our speed + float flCurrentDynamo = GetPoseParameter( m_nPoseDynamo ); + float speed = GetCurrentVelocity().Length(); + float flDynamoSpeed = (maxSpeed > 0 ? speed / maxSpeed : 1.0) * 60; + flCurrentDynamo -= flDynamoSpeed; + if ( flCurrentDynamo < -180.0 ) + { + flCurrentDynamo += 360.0; + } + SetPoseParameter( m_nPoseDynamo, flCurrentDynamo ); + + PlayFlySound(); +} + +//----------------------------------------------------------------------------- +// Purpose: Handles movement towards the last move target. +// Input : flInterval - +//----------------------------------------------------------------------------- +bool CNPC_BaseScanner::OverridePathMove( CBaseEntity *pMoveTarget, float flInterval ) +{ + // Save our last patrolling direction + Vector lastPatrolDir = GetNavigator()->GetCurWaypointPos() - GetAbsOrigin(); + + // Continue on our path + if ( ProgressFlyPath( flInterval, pMoveTarget, (MASK_NPCSOLID|CONTENTS_WATER), false, 64 ) == AINPP_COMPLETE ) + { + if ( IsCurSchedule( SCHED_SCANNER_PATROL ) ) + { + m_vLastPatrolDir = lastPatrolDir; + VectorNormalize(m_vLastPatrolDir); + } + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flInterval - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CNPC_BaseScanner::OverrideMove( float flInterval ) +{ + // ---------------------------------------------- + // If dive bombing + // ---------------------------------------------- + if (m_nFlyMode == SCANNER_FLY_DIVE) + { + MoveToDivebomb( flInterval ); + } + else + { + Vector vMoveTargetPos(0,0,0); + CBaseEntity *pMoveTarget = NULL; + + // The original line of code was, due to the accidental use of '|' instead of + // '&', always true. Replacing with 'true' to suppress the warning without changing + // the (long-standing) behavior. + if ( true ) //!GetNavigator()->IsGoalActive() || ( GetNavigator()->GetCurWaypointFlags() | bits_WP_TO_PATHCORNER ) ) + { + // Select move target + if ( GetTarget() != NULL ) + { + pMoveTarget = GetTarget(); + } + else if ( GetEnemy() != NULL ) + { + pMoveTarget = GetEnemy(); + } + + // Select move target position + if ( GetEnemy() != NULL ) + { + vMoveTargetPos = GetEnemy()->GetAbsOrigin(); + } + } + else + { + vMoveTargetPos = GetNavigator()->GetCurWaypointPos(); + } + + ClearCondition( COND_SCANNER_FLY_CLEAR ); + ClearCondition( COND_SCANNER_FLY_BLOCKED ); + + // See if we can fly there directly + if ( pMoveTarget ) + { + trace_t tr; + AI_TraceHull( GetAbsOrigin(), vMoveTargetPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); + + float fTargetDist = (1.0f-tr.fraction)*(GetAbsOrigin() - vMoveTargetPos).Length(); + + if ( ( tr.m_pEnt == pMoveTarget ) || ( fTargetDist < 50 ) ) + { + if ( g_debug_basescanner.GetBool() ) + { + NDebugOverlay::Line(GetLocalOrigin(), vMoveTargetPos, 0,255,0, true, 0); + NDebugOverlay::Cross3D(tr.endpos,Vector(-5,-5,-5),Vector(5,5,5),0,255,0,true,0.1); + } + + SetCondition( COND_SCANNER_FLY_CLEAR ); + } + else + { + //HANDY DEBUG TOOL + if ( g_debug_basescanner.GetBool() ) + { + NDebugOverlay::Line(GetLocalOrigin(), vMoveTargetPos, 255,0,0, true, 0); + NDebugOverlay::Cross3D(tr.endpos,Vector(-5,-5,-5),Vector(5,5,5),255,0,0,true,0.1); + } + + SetCondition( COND_SCANNER_FLY_BLOCKED ); + } + } + + // If I have a route, keep it updated and move toward target + if ( GetNavigator()->IsGoalActive() ) + { + if ( OverridePathMove( pMoveTarget, flInterval ) ) + { + BlendPhyscannonLaunchSpeed(); + return true; + } + } + // ---------------------------------------------- + // If attacking + // ---------------------------------------------- + else if (m_nFlyMode == SCANNER_FLY_ATTACK) + { + MoveToAttack( flInterval ); + } + // ----------------------------------------------------------------- + // If I don't have a route, just decelerate + // ----------------------------------------------------------------- + else if (!GetNavigator()->IsGoalActive()) + { + float myDecay = 9.5; + Decelerate( flInterval, myDecay); + } + } + + MoveExecute_Alive( flInterval ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &goalPos - +// &startPos - +// idealRange - +// idealHeight - +// Output : Vector +//----------------------------------------------------------------------------- +Vector CNPC_BaseScanner::IdealGoalForMovement( const Vector &goalPos, const Vector &startPos, float idealRange, float idealHeightDiff ) +{ + Vector vMoveDir; + + if ( GetGoalDirection( &vMoveDir ) == false ) + { + vMoveDir = ( goalPos - startPos ); + vMoveDir.z = 0; + VectorNormalize( vMoveDir ); + } + + // Move up from the position by the desired amount + Vector vIdealPos = goalPos + Vector( 0, 0, idealHeightDiff ) + ( vMoveDir * -idealRange ); + + // Trace down and make sure we can fit here + trace_t tr; + AI_TraceHull( vIdealPos, vIdealPos - Vector( 0, 0, MinGroundDist() ), GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr ); + + // Move up otherwise + if ( tr.fraction < 1.0f ) + { + vIdealPos.z += ( MinGroundDist() * ( 1.0f - tr.fraction ) ); + } + + //FIXME: We also need to make sure that we fit here at all, and if not, chose a new spot + + // Debug tools + if ( g_debug_basescanner.GetBool() ) + { + NDebugOverlay::Cross3D( goalPos, -Vector(8,8,8), Vector(8,8,8), 255, 255, 0, true, 0.1f ); + NDebugOverlay::Cross3D( startPos, -Vector(8,8,8), Vector(8,8,8), 255, 0, 255, true, 0.1f ); + NDebugOverlay::Cross3D( vIdealPos, -Vector(8,8,8), Vector(8,8,8), 255, 255, 255, true, 0.1f ); + NDebugOverlay::Line( startPos, goalPos, 0, 255, 0, true, 0.1f ); + + NDebugOverlay::Cross3D( goalPos + ( vMoveDir * -idealRange ), -Vector(8,8,8), Vector(8,8,8), 255, 255, 255, true, 0.1f ); + NDebugOverlay::Line( goalPos, goalPos + ( vMoveDir * -idealRange ), 255, 255, 0, true, 0.1f ); + NDebugOverlay::Line( goalPos + ( vMoveDir * -idealRange ), vIdealPos, 255, 255, 0, true, 0.1f ); + } + + return vIdealPos; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flInterval - +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::MoveToAttack(float flInterval) +{ + if (GetEnemy() == NULL) + return; + + if ( flInterval <= 0 ) + return; + + Vector vTargetPos = GetEnemyLKP(); + + //float flDesiredDist = m_flAttackNearDist + ( ( m_flAttackFarDist - m_flAttackNearDist ) / 2 ); + + Vector idealPos = IdealGoalForMovement( vTargetPos, GetAbsOrigin(), GetGoalDistance(), m_flAttackNearDist ); + + MoveToTarget( flInterval, idealPos ); + + //FIXME: Re-implement? + + /* + // --------------------------------------------------------- + // Add evasion if I have taken damage recently + // --------------------------------------------------------- + if ((m_flLastDamageTime + SCANNER_EVADE_TIME) > gpGlobals->curtime) + { + vFlyDirection = vFlyDirection + VelocityToEvade(GetEnemyCombatCharacterPointer()); + } + */ +} + +//----------------------------------------------------------------------------- +// Purpose: Accelerates toward a given position. +// Input : flInterval - Time interval over which to move. +// vecMoveTarget - Position to move toward. +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::MoveToTarget( float flInterval, const Vector &vecMoveTarget ) +{ + // Don't move if stalling + if ( m_flEngineStallTime > gpGlobals->curtime ) + return; + + // Look at our inspection target if we have one + if ( GetEnemy() != NULL ) + { + // Otherwise at our enemy + TurnHeadToTarget( flInterval, GetEnemy()->EyePosition() ); + } + else + { + // Otherwise face our motion direction + TurnHeadToTarget( flInterval, vecMoveTarget ); + } + + // ------------------------------------- + // Move towards our target + // ------------------------------------- + float myAccel; + float myZAccel = 400.0f; + float myDecay = 0.15f; + + Vector vecCurrentDir; + + // Get the relationship between my current velocity and the way I want to be going. + vecCurrentDir = GetCurrentVelocity(); + VectorNormalize( vecCurrentDir ); + + Vector targetDir = vecMoveTarget - GetAbsOrigin(); + float flDist = VectorNormalize(targetDir); + + float flDot; + flDot = DotProduct( targetDir, vecCurrentDir ); + + if( flDot > 0.25 ) + { + // If my target is in front of me, my flight model is a bit more accurate. + myAccel = 250; + } + else + { + // Have a harder time correcting my course if I'm currently flying away from my target. + myAccel = 128; + } + + if ( myAccel > flDist / flInterval ) + { + myAccel = flDist / flInterval; + } + + if ( myZAccel > flDist / flInterval ) + { + myZAccel = flDist / flInterval; + } + + MoveInDirection( flInterval, targetDir, myAccel, myZAccel, myDecay ); + + // calc relative banking targets + Vector forward, right, up; + GetVectors( &forward, &right, &up ); + + m_vCurrentBanking.x = targetDir.x; + m_vCurrentBanking.z = 120.0f * DotProduct( right, targetDir ); + m_vCurrentBanking.y = 0; + + float speedPerc = SimpleSplineRemapVal( GetCurrentVelocity().Length(), 0.0f, GetMaxSpeed(), 0.0f, 1.0f ); + + speedPerc = clamp( speedPerc, 0.0f, 1.0f ); + + m_vCurrentBanking *= speedPerc; +} + +//----------------------------------------------------------------------------- +// Danger sounds. +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::DiveBombSoundThink() +{ + Vector vecPosition, vecVelocity; + IPhysicsObject *pPhysicsObject = VPhysicsGetObject(); + + if ( pPhysicsObject == NULL ) + return; + + pPhysicsObject->GetPosition( &vecPosition, NULL ); + pPhysicsObject->GetVelocity( &vecVelocity, NULL ); + + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + if ( pPlayer ) + { + Vector vecDelta; + VectorSubtract( pPlayer->GetAbsOrigin(), vecPosition, vecDelta ); + VectorNormalize( vecDelta ); + if ( DotProduct( vecDelta, vecVelocity ) > 0.5f ) + { + Vector vecEndPoint; + VectorMA( vecPosition, 2.0f * TICK_INTERVAL, vecVelocity, vecEndPoint ); + float flDist = CalcDistanceToLineSegment( pPlayer->GetAbsOrigin(), vecPosition, vecEndPoint ); + if ( flDist < 200.0f ) + { + ScannerEmitSound( "DiveBombFlyby" ); + SetContextThink( &CNPC_BaseScanner::DiveBombSoundThink, gpGlobals->curtime + 0.5f, s_pDiveBombSoundThinkContext ); + return; + } + } + } + + SetContextThink( &CNPC_BaseScanner::DiveBombSoundThink, gpGlobals->curtime + 2.0f * TICK_INTERVAL, s_pDiveBombSoundThinkContext ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flInterval - +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::MoveToDivebomb(float flInterval) +{ + float myAccel = 1600; + float myDecay = 0.05f; // decay current velocity to 10% in 1 second + + // Fly towards my enemy + Vector vEnemyPos = GetEnemyLKP(); + Vector vFlyDirection = vEnemyPos - GetLocalOrigin(); + VectorNormalize( vFlyDirection ); + + // Set net velocity + MoveInDirection( flInterval, m_vecDiveBombDirection, myAccel, myAccel, myDecay); + + // Spin out of control. + Vector forward; + VPhysicsGetObject()->LocalToWorldVector( &forward, Vector( 1.0, 0.0, 0.0 ) ); + AngularImpulse torque = forward * m_flDiveBombRollForce; + VPhysicsGetObject()->ApplyTorqueCenter( torque ); + + // BUGBUG: why Y axis and not Z? + Vector up; + VPhysicsGetObject()->LocalToWorldVector( &up, Vector( 0.0, 1.0, 0.0 ) ); + VPhysicsGetObject()->ApplyForceCenter( up * 2000 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CNPC_BaseScanner::IsEnemyPlayerInSuit() +{ + if( GetEnemy() && GetEnemy()->IsPlayer() ) + { + CHL2_Player *pPlayer = NULL; + pPlayer = (CHL2_Player *)GetEnemy(); + + if( pPlayer && pPlayer->IsSuitEquipped() ) + { + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CNPC_BaseScanner::GetGoalDistance( void ) +{ + if ( m_flGoalOverrideDistance != 0.0f ) + return m_flGoalOverrideDistance; + + switch ( m_nFlyMode ) + { + case SCANNER_FLY_ATTACK: + { + float goalDist = ( m_flAttackNearDist + ( ( m_flAttackFarDist - m_flAttackNearDist ) / 2 ) ); + if( IsEnemyPlayerInSuit() ) + { + goalDist *= 0.5; + } + return goalDist; + } + break; + } + + return 128.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &vOut - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CNPC_BaseScanner::GetGoalDirection( Vector *vOut ) +{ + CBaseEntity *pTarget = GetTarget(); + + if ( pTarget == NULL ) + return false; + + if ( FClassnameIs( pTarget, "info_hint_air" ) || FClassnameIs( pTarget, "info_target" ) ) + { + AngleVectors( pTarget->GetAbsAngles(), vOut ); + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Vector CNPC_BaseScanner::VelocityToEvade(CBaseCombatCharacter *pEnemy) +{ + if (pEnemy) + { + // ----------------------------------------- + // Keep out of enemy's shooting position + // ----------------------------------------- + Vector vEnemyFacing = pEnemy->BodyDirection2D( ); + Vector vEnemyDir = pEnemy->EyePosition() - GetLocalOrigin(); + VectorNormalize(vEnemyDir); + float fDotPr = DotProduct(vEnemyFacing,vEnemyDir); + + if (fDotPr < -0.9) + { + Vector vDirUp(0,0,1); + Vector vDir; + CrossProduct( vEnemyFacing, vDirUp, vDir); + + Vector crossProduct; + CrossProduct(vEnemyFacing, vEnemyDir, crossProduct); + if (crossProduct.y < 0) + { + vDir = vDir * -1; + } + return (vDir); + } + else if (fDotPr < -0.85) + { + Vector vDirUp(0,0,1); + Vector vDir; + CrossProduct( vEnemyFacing, vDirUp, vDir); + + Vector crossProduct; + CrossProduct(vEnemyFacing, vEnemyDir, crossProduct); + if (random->RandomInt(0,1)) + { + vDir = vDir * -1; + } + return (vDir); + } + } + return vec3_origin; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CNPC_BaseScanner::DrawDebugTextOverlays(void) +{ + int nOffset = BaseClass::DrawDebugTextOverlays(); + + if ( m_debugOverlays & OVERLAY_TEXT_BIT ) + { + Vector vel; + GetVelocity( &vel, NULL ); + + char tempstr[512]; + Q_snprintf( tempstr, sizeof(tempstr), "speed (max): %.2f (%.2f)", vel.Length(), m_flSpeed ); + EntityText( nOffset, tempstr, 0 ); + nOffset++; + } + + return nOffset; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CNPC_BaseScanner::GetHeadTurnRate( void ) +{ + if ( GetEnemy() ) + return 800.0f; + + return 350.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +inline CBaseEntity *CNPC_BaseScanner::EntityToWatch( void ) +{ + return ( GetTarget() != NULL ) ? GetTarget() : GetEnemy(); // Okay if NULL +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flInterval - +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::UpdateHead( float flInterval ) +{ + float yaw = GetPoseParameter( m_nPoseFaceHoriz ); + float pitch = GetPoseParameter( m_nPoseFaceVert ); + + CBaseEntity *pTarget = EntityToWatch(); + + Vector vLookPos; + + if ( !HasCondition( COND_IN_PVS ) || GetAttachment( "eyes", vLookPos ) == false ) + { + vLookPos = EyePosition(); + } + + if ( pTarget != NULL ) + { + Vector lookDir = pTarget->EyePosition() - vLookPos; + VectorNormalize( lookDir ); + + if ( DotProduct( lookDir, BodyDirection3D() ) < 0.0f ) + { + SetPoseParameter( m_nPoseFaceHoriz, UTIL_Approach( 0, yaw, 10 ) ); + SetPoseParameter( m_nPoseFaceVert, UTIL_Approach( 0, pitch, 10 ) ); + + return; + } + + float facingYaw = VecToYaw( BodyDirection3D() ); + float yawDiff = VecToYaw( lookDir ); + yawDiff = UTIL_AngleDiff( yawDiff, facingYaw + yaw ); + + float facingPitch = UTIL_VecToPitch( BodyDirection3D() ); + float pitchDiff = UTIL_VecToPitch( lookDir ); + pitchDiff = UTIL_AngleDiff( pitchDiff, facingPitch + pitch ); + + SetPoseParameter( m_nPoseFaceHoriz, UTIL_Approach( yaw + yawDiff, yaw, 50 ) ); + SetPoseParameter( m_nPoseFaceVert, UTIL_Approach( pitch + pitchDiff, pitch, 50 ) ); + } + else + { + SetPoseParameter( m_nPoseFaceHoriz, UTIL_Approach( 0, yaw, 10 ) ); + SetPoseParameter( m_nPoseFaceVert, UTIL_Approach( 0, pitch, 10 ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &linear - +// &angular - +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::ClampMotorForces( Vector &linear, AngularImpulse &angular ) +{ + // limit reaction forces + if ( m_nFlyMode != SCANNER_FLY_DIVE ) + { + linear.x = clamp( linear.x, -500, 500 ); + linear.y = clamp( linear.y, -500, 500 ); + linear.z = clamp( linear.z, -500, 500 ); + } + + // If we're dive bombing, we need to drop faster than normal + if ( m_nFlyMode != SCANNER_FLY_DIVE ) + { + // Add in weightlessness + linear.z += 800; + } + + angular.z = clamp( angular.z, -GetHeadTurnRate(), GetHeadTurnRate() ); + if ( m_nFlyMode == SCANNER_FLY_DIVE ) + { + // Disable pitch and roll motors while crashing. + angular.x = 0; + angular.y = 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::InputSetDistanceOverride( inputdata_t &inputdata ) +{ + m_flGoalOverrideDistance = inputdata.value.Float(); +} + +//----------------------------------------------------------------------------- +// Purpose: Emit sounds specific to the NPC's state. +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::AlertSound(void) +{ + ScannerEmitSound( "Alert" ); +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CNPC_BaseScanner::DeathSound( const CTakeDamageInfo &info ) +{ + ScannerEmitSound( "Die" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Overridden so that scanners play battle sounds while fighting. +// Output : Returns TRUE on success, FALSE on failure. +//----------------------------------------------------------------------------- +bool CNPC_BaseScanner::ShouldPlayIdleSound( void ) +{ + if ( HasSpawnFlags( SF_NPC_GAG ) ) + return false; + + if ( random->RandomInt( 0, 25 ) != 0 ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Plays sounds while idle or in combat. +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::IdleSound(void) +{ + if ( m_NPCState == NPC_STATE_COMBAT ) + { + // dvs: the combat sounds should be related to what is happening, rather than random + ScannerEmitSound( "Combat" ); + } + else + { + ScannerEmitSound( "Idle" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Plays a sound when hurt. +//----------------------------------------------------------------------------- +void CNPC_BaseScanner::PainSound( const CTakeDamageInfo &info ) +{ + ScannerEmitSound( "Pain" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CNPC_BaseScanner::GetMaxSpeed() +{ + return SCANNER_MAX_SPEED; +} + +//----------------------------------------------------------------------------- +// +// Schedules +// +//----------------------------------------------------------------------------- + +AI_BEGIN_CUSTOM_NPC( npc_basescanner, CNPC_BaseScanner ) + + DECLARE_TASK( TASK_SCANNER_SET_FLY_PATROL ) + DECLARE_TASK( TASK_SCANNER_SET_FLY_CHASE ) + DECLARE_TASK( TASK_SCANNER_SET_FLY_ATTACK ) + DECLARE_TASK( TASK_SCANNER_SET_FLY_DIVE ) + + DECLARE_CONDITION(COND_SCANNER_FLY_CLEAR) + DECLARE_CONDITION(COND_SCANNER_FLY_BLOCKED) + DECLARE_CONDITION(COND_SCANNER_RELEASED_FROM_PHYSCANNON) + DECLARE_CONDITION(COND_SCANNER_GRABBED_BY_PHYSCANNON) + + //========================================================= + // > SCHED_SCANNER_PATROL + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_SCANNER_PATROL, + + " Tasks" + " TASK_SCANNER_SET_FLY_PATROL 0" + " TASK_SET_TOLERANCE_DISTANCE 32" + " TASK_SET_ROUTE_SEARCH_TIME 5" // Spend 5 seconds trying to build a path if stuck + " TASK_GET_PATH_TO_RANDOM_NODE 2000" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + "" + " Interrupts" + " COND_GIVE_WAY" + " COND_NEW_ENEMY" + " COND_SEE_ENEMY" + " COND_SEE_FEAR" + " COND_HEAR_COMBAT" + " COND_HEAR_DANGER" + " COND_HEAR_PLAYER" + " COND_LIGHT_DAMAGE" + " COND_HEAVY_DAMAGE" + " COND_PROVOKED" + " COND_SCANNER_GRABBED_BY_PHYSCANNON" + ) + + //========================================================= + // > SCHED_SCANNER_ATTACK + // + // This task does nothing. Translate it in your derived + // class to perform your attack. + // + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_SCANNER_ATTACK, + + " Tasks" + " TASK_SCANNER_SET_FLY_ATTACK 0" + " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" + " TASK_WAIT 0.1" + "" + " Interrupts" + " COND_TOO_FAR_TO_ATTACK" + " COND_SCANNER_FLY_BLOCKED" + " COND_NEW_ENEMY" + " COND_SCANNER_GRABBED_BY_PHYSCANNON" + ) + + //========================================================= + // > SCHED_SCANNER_ATTACK_HOVER + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_SCANNER_ATTACK_HOVER, + + " Tasks" + " TASK_SCANNER_SET_FLY_ATTACK 0" + " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" + " TASK_WAIT 0.1" + "" + " Interrupts" + " COND_TOO_FAR_TO_ATTACK" + " COND_SCANNER_FLY_BLOCKED" + " COND_NEW_ENEMY" + " COND_SCANNER_GRABBED_BY_PHYSCANNON" + ) + + //========================================================= + // > SCHED_SCANNER_ATTACK_DIVEBOMB + // + // Only done when scanner is dead + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_SCANNER_ATTACK_DIVEBOMB, + + " Tasks" + " TASK_SCANNER_SET_FLY_DIVE 0" + " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" + " TASK_WAIT 10" + "" + " Interrupts" + " COND_SCANNER_GRABBED_BY_PHYSCANNON" + ) + + //========================================================= + // > SCHED_SCANNER_CHASE_ENEMY + // + // Different interrupts than normal chase enemy. + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_SCANNER_CHASE_ENEMY, + + " Tasks" + " TASK_SCANNER_SET_FLY_CHASE 0" + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCANNER_PATROL" + " TASK_SET_TOLERANCE_DISTANCE 120" + " TASK_GET_PATH_TO_ENEMY 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + "" + "" + " Interrupts" + " COND_SCANNER_FLY_CLEAR" + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + " COND_LOST_ENEMY" + " COND_SCANNER_GRABBED_BY_PHYSCANNON" + ) + + //========================================================= + // > SCHED_SCANNER_CHASE_TARGET + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_SCANNER_CHASE_TARGET, + + " Tasks" + " TASK_SCANNER_SET_FLY_CHASE 0" + " TASK_SET_TOLERANCE_DISTANCE 64" + " TASK_GET_PATH_TO_TARGET 0" //FIXME: This is wrong! + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + "" + " Interrupts" + " COND_SCANNER_FLY_CLEAR" + " COND_NEW_ENEMY" + " COND_SCANNER_GRABBED_BY_PHYSCANNON" + ) + + //========================================================= + // > SCHED_SCANNER_FOLLOW_HOVER + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_SCANNER_FOLLOW_HOVER, + + " Tasks" + " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" + " TASK_WAIT 0.1" + "" + " Interrupts" + " COND_SCANNER_FLY_BLOCKED" + " COND_SCANNER_GRABBED_BY_PHYSCANNON" + ) + + //========================================================= + // > SCHED_SCANNER_HELD_BY_PHYSCANNON + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_SCANNER_HELD_BY_PHYSCANNON, + + " Tasks" + " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" + " TASK_WAIT 5.0" + "" + " Interrupts" + " COND_LIGHT_DAMAGE" + " COND_HEAVY_DAMAGE" + " COND_SCANNER_RELEASED_FROM_PHYSCANNON" + ) + +AI_END_CUSTOM_NPC() |