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