diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/hl2/cbasehelicopter.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/hl2/cbasehelicopter.cpp')
| -rw-r--r-- | mp/src/game/server/hl2/cbasehelicopter.cpp | 3194 |
1 files changed, 1597 insertions, 1597 deletions
diff --git a/mp/src/game/server/hl2/cbasehelicopter.cpp b/mp/src/game/server/hl2/cbasehelicopter.cpp index a83783f6..1243fe18 100644 --- a/mp/src/game/server/hl2/cbasehelicopter.cpp +++ b/mp/src/game/server/hl2/cbasehelicopter.cpp @@ -1,1597 +1,1597 @@ -//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose: Base class for helicopters & helicopter-type vehicles
-//
-// $NoKeywords: $
-//=============================================================================//
-#include "cbase.h"
-#include "ai_network.h"
-#include "ai_default.h"
-#include "ai_schedule.h"
-#include "ai_hull.h"
-#include "ai_node.h"
-#include "ai_task.h"
-#include "ai_senses.h"
-#include "ai_memory.h"
-#include "entitylist.h"
-#include "soundenvelope.h"
-#include "gamerules.h"
-#include "grenade_homer.h"
-#include "ndebugoverlay.h"
-#include "cbasehelicopter.h"
-#include "soundflags.h"
-#include "rope.h"
-#include "saverestore_utlvector.h"
-#include "collisionutils.h"
-#include "coordsize.h"
-#include "effects.h"
-#include "rotorwash.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-void ExpandBBox(Vector &vecMins, Vector &vecMaxs);
-
-#if 0
-virtual void NullThink( void );
-#endif //0
-
-#define HELICOPTER_THINK_INTERVAL 0.1
-#define HELICOPTER_ROTORWASH_THINK_INTERVAL 0.01
-#define BASECHOPPER_DEBUG_WASH 1
-
-ConVar g_debug_basehelicopter( "g_debug_basehelicopter", "0", FCVAR_CHEAT );
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-// TODOs
-//
-// -Member function: CHANGE MOVE GOAL
-//
-// -Member function: GET GRAVITY (or GetMaxThrust)
-//
-//---------------------------------------------------------
-//---------------------------------------------------------
-
-static const char *s_pRotorWashThinkContext = "RotorWashThink";
-static const char *s_pDelayedKillThinkContext = "DelayedKillThink";
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-
-BEGIN_DATADESC_NO_BASE( washentity_t )
- DEFINE_FIELD( hEntity, FIELD_EHANDLE ),
- DEFINE_FIELD( flWashStartTime, FIELD_TIME ),
-END_DATADESC()
-
-
-BEGIN_DATADESC( CBaseHelicopter )
-
- DEFINE_THINKFUNC( HelicopterThink ),
- DEFINE_THINKFUNC( RotorWashThink ),
- DEFINE_THINKFUNC( CallDyingThink ),
- DEFINE_THINKFUNC( DelayedKillThink ),
- DEFINE_ENTITYFUNC( CrashTouch ),
- DEFINE_ENTITYFUNC( FlyTouch ),
-
- DEFINE_SOUNDPATCH( m_pRotorSound ),
- DEFINE_SOUNDPATCH( m_pRotorBlast ),
- DEFINE_FIELD( m_flForce, FIELD_FLOAT ),
- DEFINE_FIELD( m_fHelicopterFlags, FIELD_INTEGER),
- DEFINE_FIELD( m_vecDesiredFaceDir, FIELD_VECTOR ),
- DEFINE_FIELD( m_flLastSeen, FIELD_TIME ),
- DEFINE_FIELD( m_flPrevSeen, FIELD_TIME ),
-// DEFINE_FIELD( m_iSoundState, FIELD_INTEGER ), // Don't save, precached
- DEFINE_FIELD( m_vecTargetPosition, FIELD_POSITION_VECTOR ),
-
- DEFINE_FIELD( m_hRotorWash, FIELD_EHANDLE ),
-
- DEFINE_FIELD( m_flMaxSpeed, FIELD_FLOAT ),
- DEFINE_FIELD( m_flMaxSpeedFiring, FIELD_FLOAT ),
- DEFINE_FIELD( m_flGoalSpeed, FIELD_FLOAT ),
- DEFINE_KEYFIELD( m_flInitialSpeed, FIELD_FLOAT, "InitialSpeed" ),
-
- DEFINE_FIELD( m_flRandomOffsetTime, FIELD_TIME ),
- DEFINE_FIELD( m_vecRandomOffset, FIELD_VECTOR ),
- DEFINE_FIELD( m_flRotorWashEntitySearchTime, FIELD_TIME ),
- DEFINE_FIELD( m_bSuppressSound, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_flStartupTime, FIELD_TIME ),
-
- DEFINE_FIELD( m_cullBoxMins, FIELD_VECTOR ),
- DEFINE_FIELD( m_cullBoxMaxs, FIELD_VECTOR ),
-
- DEFINE_UTLVECTOR( m_hEntitiesPushedByWash, FIELD_EMBEDDED ),
-
- // Inputs
- DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate),
- DEFINE_INPUTFUNC( FIELD_VOID, "GunOn", InputGunOn ),
- DEFINE_INPUTFUNC( FIELD_VOID, "GunOff", InputGunOff ),
- DEFINE_INPUTFUNC( FIELD_VOID, "MissileOn", InputMissileOn ),
- DEFINE_INPUTFUNC( FIELD_VOID, "MissileOff", InputMissileOff ),
- DEFINE_INPUTFUNC( FIELD_VOID, "EnableRotorWash", InputEnableRotorWash ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DisableRotorWash", InputDisableRotorWash ),
- DEFINE_INPUTFUNC( FIELD_VOID, "MoveTopSpeed", InputMoveTopSpeed ),
- DEFINE_INPUTFUNC( FIELD_FLOAT, "MoveSpecifiedSpeed", InputMoveSpecifiedSpeed ),
- DEFINE_INPUTFUNC( FIELD_STRING, "SetAngles", InputSetAngles ),
- DEFINE_INPUTFUNC( FIELD_VOID, "EnableRotorSound", InputEnableRotorSound ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DisableRotorSound", InputDisableRotorSound ),
- DEFINE_INPUTFUNC( FIELD_VOID, "Kill", InputKill ),
-
-END_DATADESC()
-
-IMPLEMENT_SERVERCLASS_ST( CBaseHelicopter, DT_BaseHelicopter )
- SendPropTime( SENDINFO( m_flStartupTime ) ),
-END_SEND_TABLE()
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-CBaseHelicopter::CBaseHelicopter( void )
-{
- m_cullBoxMins = vec3_origin;
- m_cullBoxMaxs = vec3_origin;
-
- m_hRotorWash = NULL;
-}
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-// Notes : Have your derived Helicopter's Spawn() function call this one FIRST
-//------------------------------------------------------------------------------
-void CBaseHelicopter::Precache( void )
-{
-}
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-// Notes : Have your derived Helicopter's Spawn() function call this one FIRST
-//------------------------------------------------------------------------------
-void CBaseHelicopter::Spawn( void )
-{
- Precache( );
-
- SetSolid( SOLID_BBOX );
- SetMoveType( MOVETYPE_STEP );
- AddFlag( FL_FLY );
- SetState( NPC_STATE_IDLE );
-
- m_lifeState = LIFE_ALIVE;
-
- // motor
- //******
- // All of this stuff is specific to the individual type of aircraft. Handle it yourself.
- //******
- // m_iAmmoType = g_pGameRules->GetAmmoDef()->Index("AR2");
- // SetModel( "models/attack_helicopter.mdl" );
- // UTIL_SetSize( this, Vector( -32, -32, -64 ), Vector( 32, 32, 0 ) );
- // UTIL_SetOrigin( this, GetLocalOrigin() );
- // m_iHealth = 100;
- // m_flFieldOfView = -0.707; // 270 degrees
- // InitBoneControllers();
- // m_iRockets = 10;
- // Get the rotor sound started up.
-
- // This base class assumes the helicopter has no guns or missiles.
- // Set the appropriate flags in your derived class' Spawn() function.
- m_fHelicopterFlags &= ~BITS_HELICOPTER_MISSILE_ON;
- m_fHelicopterFlags &= ~BITS_HELICOPTER_GUN_ON;
-
- m_pRotorSound = NULL;
- m_pRotorBlast = NULL;
-
- SetCycle( 0 );
- ResetSequenceInfo();
-
- AddFlag( FL_NPC );
-
- m_flMaxSpeed = BASECHOPPER_MAX_SPEED;
- m_flMaxSpeedFiring = BASECHOPPER_MAX_FIRING_SPEED;
- m_takedamage = DAMAGE_AIM;
-
- // Don't start up if the level designer has asked the
- // helicopter to start disabled.
- if ( !(m_spawnflags & SF_AWAITINPUT) )
- {
- Startup();
- SetNextThink( gpGlobals->curtime + 1.0f );
- }
- else
- {
- m_flStartupTime = FLT_MAX;
- }
-
- InitPathingData( 0, BASECHOPPER_MIN_CHASE_DIST_DIFF, BASECHOPPER_AVOID_DIST );
-
- // Setup collision hull
- ExpandBBox( m_cullBoxMins, m_cullBoxMaxs );
- CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &m_cullBoxMins, &m_cullBoxMaxs );
- AddSolidFlags( FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST );
- m_flRandomOffsetTime = -1.0f;
- m_vecRandomOffset.Init( 0, 0, 0 );
-}
-
-
-//------------------------------------------------------------------------------
-// Cleanup
-//------------------------------------------------------------------------------
-void CBaseHelicopter::UpdateOnRemove()
-{
- StopRotorWash();
- BaseClass::UpdateOnRemove();
-}
-
-
-//------------------------------------------------------------------------------
-// Gets the max speed of the helicopter
-//------------------------------------------------------------------------------
-float CBaseHelicopter::GetMaxSpeed()
-{
- // If our last path_track has specified a speed, use that instead of ours
- if ( GetPathMaxSpeed() )
- return GetPathMaxSpeed();
-
- return m_flMaxSpeed;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-float CBaseHelicopter::GetMaxSpeedFiring()
-{
- // If our last path_track has specified a speed, use that instead of ours
- if ( GetPathMaxSpeed() )
- return GetPathMaxSpeed();
-
- return m_flMaxSpeedFiring;
-}
-
-
-//------------------------------------------------------------------------------
-// Enemy methods
-//------------------------------------------------------------------------------
-bool CBaseHelicopter::GetTrackPatherTarget( Vector *pPos )
-{
- if ( GetEnemy() )
- {
- *pPos = GetEnemy()->BodyTarget( GetAbsOrigin(), false );
- return true;
- }
-
- return false;
-}
-
-CBaseEntity *CBaseHelicopter::GetTrackPatherTargetEnt()
-{
- return GetEnemy();
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-bool CBaseHelicopter::FireGun( void )
-{
- return true;
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose : The main think function for the helicopters
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CBaseHelicopter::HelicopterThink( void )
-{
- CheckPVSCondition();
-
- SetNextThink( gpGlobals->curtime + HELICOPTER_THINK_INTERVAL );
-
- // Don't keep this around for more than one frame.
- ClearCondition( COND_ENEMY_DEAD );
-
- // Animate and dispatch animation events.
- StudioFrameAdvance( );
- DispatchAnimEvents( this );
-
- PrescheduleThink();
-
- if ( IsMarkedForDeletion() )
- return;
-
- ShowDamage( );
-
- // -----------------------------------------------
- // If AI is disabled, kill any motion and return
- // -----------------------------------------------
- if (CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI)
- {
- SetAbsVelocity( vec3_origin );
- SetLocalAngularVelocity( vec3_angle );
- SetNextThink( gpGlobals->curtime + HELICOPTER_THINK_INTERVAL );
- return;
- }
-
- Hunt();
-
- // Finally, forget dead enemies, or ones we've been told to ignore.
- if( GetEnemy() != NULL && (!GetEnemy()->IsAlive() || GetEnemy()->GetFlags() & FL_NOTARGET || IRelationType( GetEnemy() ) == D_NU ) )
- {
- SetEnemy( NULL );
- }
-
- HelicopterPostThink();
-}
-
-//-----------------------------------------------------------------------------
-// Rotor wash think
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::RotorWashThink( void )
-{
- if ( m_lifeState == LIFE_ALIVE || m_lifeState == LIFE_DYING )
- {
- DrawRotorWash( BASECHOPPER_WASH_ALTITUDE, GetAbsOrigin() );
- SetContextThink( &CBaseHelicopter::RotorWashThink, gpGlobals->curtime + HELICOPTER_ROTORWASH_THINK_INTERVAL, s_pRotorWashThinkContext );
- }
- else
- {
- SetContextThink( NULL, gpGlobals->curtime, s_pRotorWashThinkContext );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::DrawRotorWash( float flAltitude, const Vector &vecRotorOrigin )
-{
- // Shake any ropes nearby
- if ( random->RandomInt( 0, 2 ) == 0 )
- {
- CRopeKeyframe::ShakeRopes( GetAbsOrigin(), flAltitude, 128 );
- }
-
- if ( m_spawnflags & SF_NOROTORWASH )
- return;
-
- DoRotorPhysicsPush( vecRotorOrigin, flAltitude );
-
- if ( m_flRotorWashEntitySearchTime > gpGlobals->curtime )
- return;
-
- // Only push every half second
- m_flRotorWashEntitySearchTime = gpGlobals->curtime + 0.5f;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Push an airboat in our wash
-//-----------------------------------------------------------------------------
-#define MAX_AIRBOAT_ROLL_ANGLE 20.0f
-#define MAX_AIRBOAT_ROLL_COSANGLE 0.866f
-#define MAX_AIRBOAT_ROLL_COSANGLE_X2 0.5f
-
-void CBaseHelicopter::DoWashPushOnAirboat( CBaseEntity *pAirboat,
- const Vector &vecWashToAirboat, float flWashAmount )
-{
- // For the airboat, simply produce a small roll and a push outwards.
- // But don't produce a roll if we're too rolled in that direction already.
-
- // Get the actual up direction vector
- Vector vecUp;
- pAirboat->GetVectors( NULL, NULL, &vecUp );
- if ( vecUp.z < MAX_AIRBOAT_ROLL_COSANGLE )
- return;
-
- // Compute roll direction so that we get pushed down on the side where the rotor wash is.
- Vector vecRollNormal;
- CrossProduct( vecWashToAirboat, Vector( 0, 0, 1 ), vecRollNormal );
-
- // Project it into the plane of the roll normal
- VectorMA( vecUp, -DotProduct( vecUp, vecRollNormal ), vecRollNormal, vecUp );
- VectorNormalize( vecUp );
-
- // Compute a vector which is the max direction we can roll given the roll constraint
- Vector vecExtremeUp;
- VMatrix rot;
- MatrixBuildRotationAboutAxis( rot, vecRollNormal, MAX_AIRBOAT_ROLL_ANGLE );
- MatrixGetColumn( rot, 2, &vecExtremeUp );
-
- // Find the angle between how vertical we are and how vertical we should be
- float flCosDelta = DotProduct( vecExtremeUp, vecUp );
- float flDelta = acos(flCosDelta) * 180.0f / M_PI;
- flDelta = clamp( flDelta, 0.0f, MAX_AIRBOAT_ROLL_ANGLE );
- flDelta = SimpleSplineRemapVal( flDelta, 0.0f, MAX_AIRBOAT_ROLL_ANGLE, 0.0f, 1.0f );
-
- float flForce = 12.0f * flWashAmount * flDelta;
-
- Vector vecWashOrigin;
- Vector vecForce;
- VectorMultiply( Vector( 0, 0, -1 ), flForce, vecForce );
- VectorMA( pAirboat->GetAbsOrigin(), -200.0f, vecWashToAirboat, vecWashOrigin );
-
- pAirboat->VPhysicsTakeDamage( CTakeDamageInfo( this, this, vecForce, vecWashOrigin, flWashAmount, DMG_BLAST ) );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Push a physics object in our wash. Return false if it's now out of our wash
-//-----------------------------------------------------------------------------
-bool CBaseHelicopter::DoWashPush( washentity_t *pWash, const Vector &vecWashOrigin )
-{
- if ( !pWash || !pWash->hEntity.Get() )
- return false;
-
- // Make sure the entity is still within our wash's radius
- CBaseEntity *pEntity = pWash->hEntity;
-
- // This can happen because we can dynamically turn this flag on and off
- if ( pEntity->IsEFlagSet( EFL_NO_ROTORWASH_PUSH ))
- return false;
-
- Vector vecSpot = pEntity->BodyTarget( vecWashOrigin );
- Vector vecToSpot = ( vecSpot - vecWashOrigin );
- vecToSpot.z = 0;
- float flDist = VectorNormalize( vecToSpot );
- if ( flDist > BASECHOPPER_WASH_RADIUS )
- return false;
-
- IRotorWashShooter *pShooter = GetRotorWashShooter( pEntity );
- IPhysicsObject *pPhysObject;
-
-
- float flPushTime = (gpGlobals->curtime - pWash->flWashStartTime);
- flPushTime = clamp( flPushTime, 0, BASECHOPPER_WASH_RAMP_TIME );
- float flWashAmount = RemapVal( flPushTime, 0, BASECHOPPER_WASH_RAMP_TIME, BASECHOPPER_WASH_PUSH_MIN, BASECHOPPER_WASH_PUSH_MAX );
-
- if ( pShooter )
- {
- Vector vecForce = (0.015f / 0.1f) * flWashAmount * vecToSpot * phys_pushscale.GetFloat();
- pEntity = pShooter->DoWashPush( pWash->flWashStartTime, vecForce );
- if ( !pEntity )
- return true;
-
- washentity_t Wash;
- Wash.hEntity = pEntity;
- Wash.flWashStartTime = pWash->flWashStartTime;
- int i = m_hEntitiesPushedByWash.AddToTail( Wash );
- pWash = &m_hEntitiesPushedByWash[i];
-
- pPhysObject = pEntity->VPhysicsGetObject();
- if ( !pPhysObject )
- return true;
- }
- else
- {
- // Airboat gets special treatment
- if ( FClassnameIs( pEntity, "prop_vehicle_airboat" ) )
- {
- DoWashPushOnAirboat( pEntity, vecToSpot, flWashAmount );
- return true;
- }
-
- pPhysObject = pEntity->VPhysicsGetObject();
- if ( !pPhysObject )
- return false;
- }
-
- // Push it away from the center of the wash
- float flMass = pPhysObject->GetMass();
-
- // This used to be mass independent, which is a bad idea because it blows 200kg engine blocks
- // as much as it blows cardboard and soda cans. Make this force mass-independent, but clamp at
- // 30kg.
- flMass = MIN( flMass, 30.0f );
-
- Vector vecForce = (0.015f / 0.1f) * flWashAmount * flMass * vecToSpot * phys_pushscale.GetFloat();
- pEntity->VPhysicsTakeDamage( CTakeDamageInfo( this, this, vecForce, vecWashOrigin, flWashAmount, DMG_BLAST ) );
-
- // Debug
- if ( g_debug_basehelicopter.GetInt() == BASECHOPPER_DEBUG_WASH )
- {
- NDebugOverlay::Cross3D( pEntity->GetAbsOrigin(), -Vector(4,4,4), Vector(4,4,4), 255, 0, 0, true, 0.1f );
- NDebugOverlay::Line( pEntity->GetAbsOrigin(), pEntity->GetAbsOrigin() + vecForce, 255, 255, 0, true, 0.1f );
-
- IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject();
- Msg("Pushed %s (index %d) (mass %f) with force %f (min %.2f max %.2f) at time %.2f\n",
- pEntity->GetClassname(), pEntity->entindex(), pPhysObject->GetMass(), flWashAmount,
- BASECHOPPER_WASH_PUSH_MIN * flMass, BASECHOPPER_WASH_PUSH_MAX * flMass, gpGlobals->curtime );
- }
-
- // If we've pushed this thing for some time, remove it to give us a chance to find lighter things nearby
- if ( flPushTime > 2.0 )
- return false;
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::DoRotorPhysicsPush( const Vector &vecRotorOrigin, float flAltitude )
-{
- CBaseEntity *pEntity = NULL;
- trace_t tr;
-
- // First, trace down and find out where the was is hitting the ground
- UTIL_TraceLine( vecRotorOrigin, vecRotorOrigin+Vector(0,0,-flAltitude), (MASK_SOLID_BRUSHONLY|CONTENTS_WATER), NULL, COLLISION_GROUP_NONE, &tr );
- // Always raise the physics origin a bit
- Vector vecPhysicsOrigin = tr.endpos + Vector(0,0,64);
-
- // Debug
- if ( g_debug_basehelicopter.GetInt() == BASECHOPPER_DEBUG_WASH )
- {
- NDebugOverlay::Cross3D( vecPhysicsOrigin, -Vector(16,16,16), Vector(16,16,16), 0, 255, 255, true, 0.1f );
- }
-
- // Push entities that we've pushed before, and are still within range
- // Walk backwards because they may be removed if they're now out of range
- int iCount = m_hEntitiesPushedByWash.Count();
- bool bWasPushingObjects = (iCount > 0);
- for ( int i = (iCount-1); i >= 0; i-- )
- {
- if ( !DoWashPush( &(m_hEntitiesPushedByWash[i]), vecPhysicsOrigin ) )
- {
- // Out of range now, so remove
- m_hEntitiesPushedByWash.Remove(i);
- }
- }
-
- if ( m_flRotorWashEntitySearchTime > gpGlobals->curtime )
- return;
-
- // Any spare slots?
- iCount = m_hEntitiesPushedByWash.Count();
- if ( iCount >= BASECHOPPER_WASH_MAX_OBJECTS )
- return;
-
- // Find the lightest physics entity below us and add it to our list to push around
- CBaseEntity *pLightestEntity = NULL;
- float flLightestMass = 9999;
- while ((pEntity = gEntList.FindEntityInSphere(pEntity, vecPhysicsOrigin, BASECHOPPER_WASH_RADIUS )) != NULL)
- {
- IRotorWashShooter *pShooter = GetRotorWashShooter( pEntity );
-
- if ( pEntity->IsEFlagSet( EFL_NO_ROTORWASH_PUSH ))
- continue;
-
- if ( pShooter || pEntity->GetMoveType() == MOVETYPE_VPHYSICS || (pEntity->VPhysicsGetObject() && !pEntity->IsPlayer()) )
- {
- // Make sure it's not already in our wash
- bool bAlreadyPushing = false;
- for ( int i = 0; i < iCount; i++ )
- {
- if ( m_hEntitiesPushedByWash[i].hEntity == pEntity )
- {
- bAlreadyPushing = true;
- break;
- }
- }
- if ( bAlreadyPushing )
- continue;
-
- float flMass = FLT_MAX;
- if ( pShooter )
- {
- flMass = 1.0f;
- }
- else
- {
- // Don't try to push anything too big
- IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject();
- if ( pPhysObject )
- {
- flMass = pPhysObject->GetMass();
- if ( flMass > BASECHOPPER_WASH_MAX_MASS )
- continue;
- }
- }
-
- // Ignore anything bigger than the one we've already found
- if ( flMass > flLightestMass )
- continue;
-
- Vector vecSpot = pEntity->BodyTarget( vecPhysicsOrigin );
-
- // Don't push things too far below our starting point (helps reduce through-roof cases w/o doing a trace)
- if ( fabs( vecSpot.z - vecPhysicsOrigin.z ) > 96 )
- continue;
-
- Vector vecToSpot = ( vecSpot - vecPhysicsOrigin );
- vecToSpot.z = 0;
- float flDist = VectorNormalize( vecToSpot );
- if ( flDist > BASECHOPPER_WASH_RADIUS )
- continue;
-
-
- // Try to cast to the helicopter; if we can't, then we can't be hit.
- if ( pEntity->GetServerVehicle() )
- {
- UTIL_TraceLine( vecSpot, vecPhysicsOrigin, MASK_SOLID_BRUSHONLY, pEntity, COLLISION_GROUP_NONE, &tr );
- if ( tr.fraction != 1.0f )
- continue;
- }
-
- flLightestMass = flMass;
- pLightestEntity = pEntity;
-
- washentity_t Wash;
- Wash.hEntity = pLightestEntity;
- Wash.flWashStartTime = gpGlobals->curtime;
- m_hEntitiesPushedByWash.AddToTail( Wash );
-
- // Can we fit more after adding this one? No? Then we are done.
- iCount = m_hEntitiesPushedByWash.Count();
- if ( iCount >= BASECHOPPER_WASH_MAX_OBJECTS )
- break;
- }
- }
-
- // Handle sound.
- // If we just started pushing objects, ramp the blast sound up.
- if ( !bWasPushingObjects && m_hEntitiesPushedByWash.Count() )
- {
- if ( m_pRotorBlast )
- {
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- controller.SoundChangeVolume( m_pRotorBlast, 1.0, 1.0 );
- }
- }
- else if ( bWasPushingObjects && m_hEntitiesPushedByWash.Count() == 0 )
- {
- if ( m_pRotorBlast )
- {
- // We just stopped pushing objects, so fade the blast sound out.
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- controller.SoundChangeVolume( m_pRotorBlast, 0, 1.0 );
- }
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Updates the enemy
-//------------------------------------------------------------------------------
-float CBaseHelicopter::EnemySearchDistance( )
-{
- return 4092;
-}
-
-
-//------------------------------------------------------------------------------
-// Updates the enemy
-//------------------------------------------------------------------------------
-void CBaseHelicopter::UpdateEnemy()
-{
- if( HasCondition( COND_ENEMY_DEAD ) )
- {
- SetEnemy( NULL );
- }
-
- // Look for my best enemy. If I change enemies,
- // be sure and change my prevseen/lastseen timers.
- if( m_lifeState == LIFE_ALIVE )
- {
- GetSenses()->Look( EnemySearchDistance() );
-
- GetEnemies()->RefreshMemories();
- ChooseEnemy();
-
- if( HasEnemy() )
- {
- CBaseEntity *pEnemy = GetEnemy();
- GatherEnemyConditions( pEnemy );
- if ( FVisible( pEnemy ) )
- {
- if (m_flLastSeen < gpGlobals->curtime - 2)
- {
- m_flPrevSeen = gpGlobals->curtime;
- }
-
- m_flLastSeen = gpGlobals->curtime;
- m_vecTargetPosition = pEnemy->WorldSpaceCenter();
- }
- }
- else
- {
- // look at where we're going instead
- m_vecTargetPosition = GetDesiredPosition();
- }
- }
- else
- {
- // If we're dead or dying, forget our enemy and don't look for new ones(sjb)
- SetEnemy( NULL );
- }
-
-}
-
-//------------------------------------------------------------------------------
-// Purpose : Override the desired position if your derived helicopter is doing something special
-//------------------------------------------------------------------------------
-void CBaseHelicopter::UpdateDesiredPosition( void )
-{
-}
-
-//------------------------------------------------------------------------------
-// Updates the facing direction
-//------------------------------------------------------------------------------
-void CBaseHelicopter::UpdateFacingDirection()
-{
- if ( 1 )
- {
- Vector targetDir = m_vecTargetPosition - GetAbsOrigin();
- Vector desiredDir = GetDesiredPosition() - GetAbsOrigin();
-
- VectorNormalize( targetDir );
- VectorNormalize( desiredDir );
-
- if ( !IsCrashing() && m_flLastSeen + 5 > gpGlobals->curtime ) //&& DotProduct( targetDir, desiredDir) > 0.25)
- {
- // If we've seen the target recently, face the target.
- //Msg( "Facing Target \n" );
- m_vecDesiredFaceDir = targetDir;
- }
- else
- {
- // Face our desired position.
- // Msg( "Facing Position\n" );
- m_vecDesiredFaceDir = desiredDir;
- }
- }
- else
- {
- // Face the way the path corner tells us to.
- //Msg( "Facing my path corner\n" );
- m_vecDesiredFaceDir = GetGoalOrientation();
- }
-
-}
-
-
-//------------------------------------------------------------------------------
-// Fire weapons
-//------------------------------------------------------------------------------
-void CBaseHelicopter::FireWeapons()
-{
- // ALERT( at_console, "%.0f %.0f %.0f\n", gpGlobals->curtime, m_flLastSeen, m_flPrevSeen );
- if (m_fHelicopterFlags & BITS_HELICOPTER_GUN_ON)
- {
- //if ( (m_flLastSeen + 1 > gpGlobals->curtime) && (m_flPrevSeen + 2 < gpGlobals->curtime) )
- {
- if (FireGun( ))
- {
- // slow down if we're firing
- if (m_flGoalSpeed > GetMaxSpeedFiring() )
- {
- m_flGoalSpeed = GetMaxSpeedFiring();
- }
- }
- }
- }
-
- if (m_fHelicopterFlags & BITS_HELICOPTER_MISSILE_ON)
- {
- AimRocketGun();
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CBaseHelicopter::Hunt( void )
-{
- UpdateEnemy();
-
- UpdateTrackNavigation( );
-
- UpdateDesiredPosition();
-
- UpdateFacingDirection();
-
- Flight();
-
- UpdatePlayerDopplerShift( );
-
- FireWeapons();
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CBaseHelicopter::UpdatePlayerDopplerShift( )
-{
- // -----------------------------
- // make rotor, engine sounds
- // -----------------------------
- if (m_iSoundState == 0)
- {
- // Sound startup.
- InitializeRotorSound();
- }
- else
- {
- CBaseEntity *pPlayer = NULL;
-
- // UNDONE: this needs to send different sounds to every player for multiplayer.
- // FIXME: this isn't the correct way to find a player!!!
- pPlayer = gEntList.FindEntityByName( NULL, "!player" );
- if (pPlayer)
- {
- Vector dir;
- VectorSubtract( pPlayer->GetAbsOrigin(), GetAbsOrigin(), dir );
- VectorNormalize(dir);
-
-#if 1
- float velReceiver = DotProduct( pPlayer->GetAbsVelocity(), dir );
- float velTransmitter = -DotProduct( GetAbsVelocity(), dir );
- // speed of sound == 13049in/s
- int iPitch = 100 * ((1 - velReceiver / 13049) / (1 + velTransmitter / 13049));
-#else
- // This is a bogus doppler shift, but I like it better
- float relV = DotProduct( GetAbsVelocity() - pPlayer->GetAbsVelocity(), dir );
- int iPitch = (int)(100 + relV / 50.0);
-#endif
-
- // clamp pitch shifts
- if (iPitch > 250)
- {
- iPitch = 250;
- }
- if (iPitch < 50)
- {
- iPitch = 50;
- }
-
- UpdateRotorSoundPitch( iPitch );
- // Msg( "Pitch:%d\n", iPitch );
- }
- else
- {
- Msg( "Chopper didn't find a player!\n" );
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Computes the actual position to fly to
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::ComputeActualTargetPosition( float flSpeed, float flTime, float flPerpDist, Vector *pDest, bool bApplyNoise )
-{
- // This is used to make the helicopter drift around a bit.
- if ( bApplyNoise && m_flRandomOffsetTime <= gpGlobals->curtime )
- {
- m_vecRandomOffset.Random( -25.0f, 25.0f );
- m_flRandomOffsetTime = gpGlobals->curtime + 1.0f;
- }
-
- if ( IsLeading() && GetEnemy() && IsOnPathTrack() )
- {
- ComputePointAlongCurrentPath( flSpeed * flTime, flPerpDist, pDest );
- *pDest += m_vecRandomOffset;
- return;
- }
-
- *pDest = GetDesiredPosition() - GetAbsOrigin();
- float flDistToDesired = pDest->Length();
- if (flDistToDesired > flSpeed * flTime)
- {
- float scale = flSpeed * flTime / flDistToDesired;
- *pDest *= scale;
- }
- else if ( IsOnPathTrack() )
- {
- // Blend in a fake destination point based on the dest velocity
- Vector vecDestVelocity;
- ComputeNormalizedDestVelocity( &vecDestVelocity );
- vecDestVelocity *= flSpeed;
-
- float flBlendFactor = 1.0f - flDistToDesired / (flSpeed * flTime);
- VectorMA( *pDest, flTime * flBlendFactor, vecDestVelocity, *pDest );
- }
-
- *pDest += GetAbsOrigin();
-
- if ( bApplyNoise )
- {
- // ComputePointAlongCurrentPath( flSpeed * flTime, flPerpDist, pDest );
- *pDest += m_vecRandomOffset;
- }
-}
-
-
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-void CBaseHelicopter::Flight( void )
-{
- if( GetFlags() & FL_ONGROUND )
- {
- //This would be really bad.
- SetGroundEntity( NULL );
- }
-
- // Generic speed up
- if (m_flGoalSpeed < GetMaxSpeed())
- {
- m_flGoalSpeed += GetAcceleration();
- }
-
- //NDebugOverlay::Line(GetAbsOrigin(), m_vecDesiredPosition, 0,0,255, true, 0.1);
-
- // tilt model 5 degrees (why?! sjb)
- QAngle vecAdj = QAngle( 5.0, 0, 0 );
-
- // estimate where I'll be facing in one seconds
- Vector forward, right, up;
- AngleVectors( GetLocalAngles() + GetLocalAngularVelocity() * 2 + vecAdj, &forward, &right, &up );
-
- // Vector vecEst1 = GetLocalOrigin() + GetAbsVelocity() + up * m_flForce - Vector( 0, 0, 384 );
- // float flSide = DotProduct( m_vecDesiredPosition - vecEst1, right );
- QAngle angVel = GetLocalAngularVelocity();
- float flSide = DotProduct( m_vecDesiredFaceDir, right );
- if (flSide < 0)
- {
- if (angVel.y < 60)
- {
- angVel.y += 8;
- }
- }
- else
- {
- if (angVel.y > -60)
- {
- angVel.y -= 8;
- }
- }
-
- angVel.y *= ( 0.98 ); // why?! (sjb)
-
- // estimate where I'll be in two seconds
- AngleVectors( GetLocalAngles() + angVel * 1 + vecAdj, NULL, NULL, &up );
- Vector vecEst = GetAbsOrigin() + GetAbsVelocity() * 2.0 + up * m_flForce * 20 - Vector( 0, 0, 384 * 2 );
-
- // add immediate force
- AngleVectors( GetLocalAngles() + vecAdj, &forward, &right, &up );
-
- Vector vecImpulse( 0, 0, 0 );
- vecImpulse.x += up.x * m_flForce;
- vecImpulse.y += up.y * m_flForce;
- vecImpulse.z += up.z * m_flForce;
-
- // add gravity
- vecImpulse.z -= 38.4; // 32ft/sec
- ApplyAbsVelocityImpulse( vecImpulse );
-
- float flSpeed = GetAbsVelocity().Length();
- float flDir = DotProduct( Vector( forward.x, forward.y, 0 ), Vector( GetAbsVelocity().x, GetAbsVelocity().y, 0 ) );
- if (flDir < 0)
- {
- flSpeed = -flSpeed;
- }
-
- float flDist = DotProduct( GetDesiredPosition() - vecEst, forward );
-
- // float flSlip = DotProduct( GetAbsVelocity(), right );
- float flSlip = -DotProduct( GetDesiredPosition() - vecEst, right );
-
- // fly sideways
- if (flSlip > 0)
- {
- if (GetLocalAngles().z > -30 && angVel.z > -15)
- angVel.z -= 4;
- else
- angVel.z += 2;
- }
- else
- {
- if (GetLocalAngles().z < 30 && angVel.z < 15)
- angVel.z += 4;
- else
- angVel.z -= 2;
- }
-
- // These functions contain code Ken wrote that used to be right here as part of the flight model,
- // but we want different helicopter vehicles to have different drag characteristics, so I made
- // them virtual functions (sjb)
- ApplySidewaysDrag( right );
- ApplyGeneralDrag();
-
- // apply power to stay correct height
- // FIXME: these need to be per class variables
-#define MAX_FORCE 80
-#define FORCE_POSDELTA 12
-#define FORCE_NEGDELTA 8
-
- if (m_flForce < MAX_FORCE && vecEst.z < GetDesiredPosition().z)
- {
- m_flForce += FORCE_POSDELTA;
- }
- else if (m_flForce > 30)
- {
- if (vecEst.z > GetDesiredPosition().z)
- m_flForce -= FORCE_NEGDELTA;
- }
-
- // pitch forward or back to get to target
- //-----------------------------------------
- // Pitch is reversed since Half-Life! (sjb)
- //-----------------------------------------
- if (flDist > 0 && flSpeed < m_flGoalSpeed /* && flSpeed < flDist */ && GetLocalAngles().x + angVel.x < 40)
- {
- // ALERT( at_console, "F " );
- // lean forward
- angVel.x += 12.0;
- }
- else if (flDist < 0 && flSpeed > -50 && GetLocalAngles().x + angVel.x > -20)
- {
- // ALERT( at_console, "B " );
- // lean backward
- angVel.x -= 12.0;
- }
- else if (GetLocalAngles().x + angVel.x < 0)
- {
- // ALERT( at_console, "f " );
- angVel.x += 4.0;
- }
- else if (GetLocalAngles().x + angVel.x > 0)
- {
- // ALERT( at_console, "b " );
- angVel.x -= 4.0;
- }
-
- SetLocalAngularVelocity( angVel );
- // ALERT( at_console, "%.0f %.0f : %.0f %.0f : %.0f %.0f : %.0f\n", GetAbsOrigin().x, GetAbsVelocity().x, flDist, flSpeed, GetLocalAngles().x, m_vecAngVelocity.x, m_flForce );
- // ALERT( at_console, "%.0f %.0f : %.0f %0.f : %.0f\n", GetAbsOrigin().z, GetAbsVelocity().z, vecEst.z, m_vecDesiredPosition.z, m_flForce );
-}
-
-
-//------------------------------------------------------------------------------
-// Updates the rotor wash volume
-//------------------------------------------------------------------------------
-void CBaseHelicopter::UpdateRotorWashVolume()
-{
- if ( !m_pRotorSound )
- return;
-
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- float flVolDelta = GetRotorVolume() - controller.SoundGetVolume( m_pRotorSound );
- if ( flVolDelta )
- {
- // We can change from 0 to 1 in 3 seconds.
- // Figure out how many seconds flVolDelta will take.
- float flRampTime = fabs( flVolDelta ) * 3.0f;
- controller.SoundChangeVolume( m_pRotorSound, GetRotorVolume(), flRampTime );
- }
-}
-
-
-//------------------------------------------------------------------------------
-// For scripted times where it *has* to shoot
-//------------------------------------------------------------------------------
-float CBaseHelicopter::GetRotorVolume( void )
-{
- return m_bSuppressSound ? 0.0f : 1.0f;
-}
-
-
-//-----------------------------------------------------------------------------
-// Rotor sound
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::InputEnableRotorSound( inputdata_t &inputdata )
-{
- m_bSuppressSound = false;
-}
-
-void CBaseHelicopter::InputDisableRotorSound( inputdata_t &inputdata )
-{
- m_bSuppressSound = true;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Marks the entity for deletion
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::InputKill( inputdata_t &inputdata )
-{
- StopRotorWash();
-
- m_bSuppressSound = true;
- SetContextThink( &CBaseHelicopter::DelayedKillThink, gpGlobals->curtime + 3.0f, s_pDelayedKillThinkContext );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::StopRotorWash( void )
-{
- if ( m_hRotorWash )
- {
- UTIL_Remove( m_hRotorWash );
- m_hRotorWash = NULL;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Marks the entity for deletion
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::DelayedKillThink( )
-{
- // tell owner ( if any ) that we're dead.This is mostly for NPCMaker functionality.
- CBaseEntity *pOwner = GetOwnerEntity();
- if ( pOwner )
- {
- pOwner->DeathNotice( this );
- SetOwnerEntity( NULL );
- }
-
- UTIL_Remove( this );
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CBaseHelicopter::InitializeRotorSound( void )
-{
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
-
- if ( m_pRotorSound )
- {
- // Get the rotor sound started up.
- controller.Play( m_pRotorSound, 0.0, 100 );
- UpdateRotorWashVolume();
- }
-
- if ( m_pRotorBlast )
- {
- // Start the blast sound and then immediately drop it to 0 (starting it at 0 wouldn't start it)
- controller.Play( m_pRotorBlast, 1.0, 100 );
- controller.SoundChangeVolume(m_pRotorBlast, 0, 0.0);
- }
-
- m_iSoundState = SND_CHANGE_PITCH; // hack for going through level transitions
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CBaseHelicopter::UpdateRotorSoundPitch( int iPitch )
-{
- if (m_pRotorSound)
- {
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- controller.SoundChangePitch( m_pRotorSound, iPitch, 0.1 );
- UpdateRotorWashVolume();
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CBaseHelicopter::FlyTouch( CBaseEntity *pOther )
-{
- // bounce if we hit something solid
- if ( pOther->GetSolid() == SOLID_BSP)
- {
-// trace_t tr;
-// tr = CBaseEntity::GetTouchTrace();
-
- // UNDONE, do a real bounce
- // FIXME: This causes bad problems, so we just ignore it right now
- //ApplyAbsVelocityImpulse( tr.plane.normal * (GetAbsVelocity().Length() + 200) );
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CBaseHelicopter::CrashTouch( CBaseEntity *pOther )
-{
- // only crash if we hit something solid
- if ( pOther->GetSolid() == SOLID_BSP)
- {
- SetTouch( NULL );
- SetNextThink( gpGlobals->curtime );
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CBaseHelicopter::DyingThink( void )
-{
- StudioFrameAdvance( );
- SetNextThink( gpGlobals->curtime + 0.1f );
-
- SetLocalAngularVelocity( GetLocalAngularVelocity() * 1.02 );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Override base class to add display of fly direction
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::DrawDebugGeometryOverlays(void)
-{
- if (m_pfnThink!= NULL)
- {
- // ------------------------------
- // Draw route if requested
- // ------------------------------
- if (m_debugOverlays & OVERLAY_NPC_ROUTE_BIT)
- {
- NDebugOverlay::Line(GetAbsOrigin(), GetDesiredPosition(), 0,0,255, true, 0);
- }
- }
- BaseClass::DrawDebugGeometryOverlays();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
-{
- // Take no damage from trace attacks unless it's blast damage. RadiusDamage() sometimes calls
- // TraceAttack() as a means for delivering blast damage. Usually when the explosive penetrates
- // the target. (RPG missiles do this sometimes).
- if( info.GetDamageType() & (DMG_BLAST|DMG_AIRBOAT) )
- {
- BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CBaseHelicopter::NullThink( void )
-{
- StudioFrameAdvance( );
- SetNextThink( gpGlobals->curtime + 0.5f );
-}
-
-
-void CBaseHelicopter::Startup( void )
-{
- StopRotorWash();
-
- if ( !( m_spawnflags & SF_NOROTORWASH ) )
- {
- m_hRotorWash = CreateRotorWashEmitter( GetAbsOrigin(), GetAbsAngles(), this, BASECHOPPER_WASH_ALTITUDE );
- }
-
- // Fade in the blades
- m_flStartupTime = gpGlobals->curtime;
-
- m_flGoalSpeed = m_flInitialSpeed;
- SetThink( &CBaseHelicopter::HelicopterThink );
- SetTouch( &CBaseHelicopter::FlyTouch );
- SetNextThink( gpGlobals->curtime + 0.1f );
-
- m_flRotorWashEntitySearchTime = gpGlobals->curtime;
- SetContextThink( &CBaseHelicopter::RotorWashThink, gpGlobals->curtime, s_pRotorWashThinkContext );
-}
-
-void CBaseHelicopter::StopLoopingSounds()
-{
- // Kill the rotor sounds
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- controller.SoundDestroy( m_pRotorSound );
- controller.SoundDestroy( m_pRotorBlast );
- m_pRotorSound = NULL;
- m_pRotorBlast = NULL;
-
- BaseClass::StopLoopingSounds();
-}
-
-void CBaseHelicopter::Event_Killed( const CTakeDamageInfo &info )
-{
- m_lifeState = LIFE_DYING;
-
- SetMoveType( MOVETYPE_FLYGRAVITY );
- SetGravity( UTIL_ScaleForGravity( 240 ) ); // use a lower gravity
-
- StopLoopingSounds();
-
- UTIL_SetSize( this, Vector( -32, -32, -64), Vector( 32, 32, 0) );
- SetThink( &CBaseHelicopter::CallDyingThink );
- SetTouch( &CBaseHelicopter::CrashTouch );
-
- SetNextThink( gpGlobals->curtime + 0.1f );
- m_iHealth = 0;
- m_takedamage = DAMAGE_NO;
-
-/*
- if (m_spawnflags & SF_NOWRECKAGE)
- {
- m_flNextRocket = gpGlobals->curtime + 4.0;
- }
- else
- {
- m_flNextRocket = gpGlobals->curtime + 15.0;
- }
-*/
- StopRotorWash();
-
- m_OnDeath.FireOutput( info.GetAttacker(), this );
-}
-
-
-void CBaseHelicopter::GibMonster( void )
-{
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Call Startup for a helicopter that's been flagged to start disabled
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::InputActivate( inputdata_t &inputdata )
-{
- if( m_spawnflags & SF_AWAITINPUT )
- {
- Startup();
-
- // Now clear the spawnflag to protect from
- // subsequent calls.
- m_spawnflags &= ~SF_AWAITINPUT;
- }
-}
-
-//------------------------------------------------------------------------------
-// Purpose : Turn the gun on
-//------------------------------------------------------------------------------
-void CBaseHelicopter::InputGunOn( inputdata_t &inputdata )
-{
- m_fHelicopterFlags |= BITS_HELICOPTER_GUN_ON;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Turn the gun off
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::InputGunOff( inputdata_t &inputdata )
-{
- m_fHelicopterFlags &= ~BITS_HELICOPTER_GUN_ON;
-}
-
-//------------------------------------------------------------------------------
-// Purpose : Turn the missile on
-//------------------------------------------------------------------------------
-void CBaseHelicopter::InputMissileOn( inputdata_t &inputdata )
-{
- m_fHelicopterFlags |= BITS_HELICOPTER_MISSILE_ON;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Turn the missile off
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::InputMissileOff( inputdata_t &inputdata )
-{
- m_fHelicopterFlags &= ~BITS_HELICOPTER_MISSILE_ON;
-}
-
-
-//-----------------------------------------------------------------------------
-// Enable, disable rotor wash
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::InputEnableRotorWash( inputdata_t &inputdata )
-{
- m_spawnflags &= ~SF_NOROTORWASH;
-}
-
-void CBaseHelicopter::InputDisableRotorWash( inputdata_t &inputdata )
-{
- m_spawnflags |= SF_NOROTORWASH;
-}
-
-
-//-----------------------------------------------------------------------------
-// Causes the helicopter to immediately accelerate to its desired velocity
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::InputMoveTopSpeed( inputdata_t &inputdata )
-{
- Vector vecVelocity;
- ComputeActualTargetPosition( GetMaxSpeed(), 1.0f, 0.0f, &vecVelocity, false );
- vecVelocity -= GetAbsOrigin();
-
- float flLength = VectorNormalize( vecVelocity );
- if (flLength < 1e-3)
- {
- GetVectors( &vecVelocity, NULL, NULL );
- }
-
- vecVelocity *= GetMaxSpeed();
- SetAbsVelocity( vecVelocity );
-}
-
-//-----------------------------------------------------------------------------
-// Cause helicopter to immediately accelerate to specified velocity
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::InputMoveSpecifiedSpeed( inputdata_t &inputdata )
-{
- Vector vecVelocity;
- ComputeActualTargetPosition( GetMaxSpeed(), 1.0f, 0.0f, &vecVelocity, false );
- vecVelocity -= GetAbsOrigin();
-
- float flLength = VectorNormalize( vecVelocity );
- if (flLength < 1e-3)
- {
- GetVectors( &vecVelocity, NULL, NULL );
- }
-
- float flSpeed = inputdata.value.Float();
-
- vecVelocity *= flSpeed;
- SetAbsVelocity( vecVelocity );
-}
-
-//------------------------------------------------------------------------------
-// Input values
-//------------------------------------------------------------------------------
-void CBaseHelicopter::InputSetAngles( inputdata_t &inputdata )
-{
- const char *pAngles = inputdata.value.String();
-
- QAngle angles;
- UTIL_StringToVector( angles.Base(), pAngles );
- SetAbsAngles( angles );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::ApplySidewaysDrag( const Vector &vecRight )
-{
- Vector vecNewVelocity = GetAbsVelocity();
- vecNewVelocity.x *= 1.0 - fabs( vecRight.x ) * 0.05;
- vecNewVelocity.y *= 1.0 - fabs( vecRight.y ) * 0.05;
- vecNewVelocity.z *= 1.0 - fabs( vecRight.z ) * 0.05;
- SetAbsVelocity( vecNewVelocity );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::ApplyGeneralDrag( void )
-{
- Vector vecNewVelocity = GetAbsVelocity();
- vecNewVelocity *= 0.995;
- SetAbsVelocity( vecNewVelocity );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-bool CBaseHelicopter::ChooseEnemy( void )
-{
- // See if there's a new enemy.
- CBaseEntity *pNewEnemy;
-
- pNewEnemy = BestEnemy();
-
- if ( pNewEnemy != GetEnemy() )
- {
- if ( pNewEnemy != NULL )
- {
- // New enemy! Clear the timers and set conditions.
- SetEnemy( pNewEnemy );
- m_flLastSeen = m_flPrevSeen = gpGlobals->curtime;
- }
- else
- {
- SetEnemy( NULL );
- SetState( NPC_STATE_ALERT );
- }
- return true;
- }
- else
- {
- ClearCondition( COND_NEW_ENEMY );
- return false;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::GatherEnemyConditions( CBaseEntity *pEnemy )
-{
- // -------------------
- // If enemy is dead
- // -------------------
- if ( !pEnemy->IsAlive() )
- {
- SetCondition( COND_ENEMY_DEAD );
- ClearCondition( COND_SEE_ENEMY );
- ClearCondition( COND_ENEMY_OCCLUDED );
- return;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pInfo -
-// bAlways -
-//-----------------------------------------------------------------------------
-void CBaseHelicopter::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
-{
- // Are we already marked for transmission?
- if ( pInfo->m_pTransmitEdict->Get( entindex() ) )
- return;
-
- BaseClass::SetTransmit( pInfo, bAlways );
-
- // Make our smoke trail always come with us
- if ( m_hRotorWash )
- {
- m_hRotorWash->SetTransmit( pInfo, bAlways );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void ExpandBBox(Vector &vecMins, Vector &vecMaxs)
-{
- // expand for *any* rotation
- float maxval = 0;
- for (int i = 0; i < 3; i++)
- {
- float v = fabs( vecMins[i]);
- if (v > maxval)
- maxval = v;
-
- v = fabs( vecMaxs[i]);
- if (v > maxval)
- maxval = v;
- }
-
- vecMins.Init(-maxval, -maxval, -maxval);
- vecMaxs.Init(maxval, maxval, maxval);
-}
+//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Base class for helicopters & helicopter-type vehicles +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "ai_network.h" +#include "ai_default.h" +#include "ai_schedule.h" +#include "ai_hull.h" +#include "ai_node.h" +#include "ai_task.h" +#include "ai_senses.h" +#include "ai_memory.h" +#include "entitylist.h" +#include "soundenvelope.h" +#include "gamerules.h" +#include "grenade_homer.h" +#include "ndebugoverlay.h" +#include "cbasehelicopter.h" +#include "soundflags.h" +#include "rope.h" +#include "saverestore_utlvector.h" +#include "collisionutils.h" +#include "coordsize.h" +#include "effects.h" +#include "rotorwash.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +void ExpandBBox(Vector &vecMins, Vector &vecMaxs); + +#if 0 +virtual void NullThink( void ); +#endif //0 + +#define HELICOPTER_THINK_INTERVAL 0.1 +#define HELICOPTER_ROTORWASH_THINK_INTERVAL 0.01 +#define BASECHOPPER_DEBUG_WASH 1 + +ConVar g_debug_basehelicopter( "g_debug_basehelicopter", "0", FCVAR_CHEAT ); + +//--------------------------------------------------------- +//--------------------------------------------------------- +// TODOs +// +// -Member function: CHANGE MOVE GOAL +// +// -Member function: GET GRAVITY (or GetMaxThrust) +// +//--------------------------------------------------------- +//--------------------------------------------------------- + +static const char *s_pRotorWashThinkContext = "RotorWashThink"; +static const char *s_pDelayedKillThinkContext = "DelayedKillThink"; + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ + +BEGIN_DATADESC_NO_BASE( washentity_t ) + DEFINE_FIELD( hEntity, FIELD_EHANDLE ), + DEFINE_FIELD( flWashStartTime, FIELD_TIME ), +END_DATADESC() + + +BEGIN_DATADESC( CBaseHelicopter ) + + DEFINE_THINKFUNC( HelicopterThink ), + DEFINE_THINKFUNC( RotorWashThink ), + DEFINE_THINKFUNC( CallDyingThink ), + DEFINE_THINKFUNC( DelayedKillThink ), + DEFINE_ENTITYFUNC( CrashTouch ), + DEFINE_ENTITYFUNC( FlyTouch ), + + DEFINE_SOUNDPATCH( m_pRotorSound ), + DEFINE_SOUNDPATCH( m_pRotorBlast ), + DEFINE_FIELD( m_flForce, FIELD_FLOAT ), + DEFINE_FIELD( m_fHelicopterFlags, FIELD_INTEGER), + DEFINE_FIELD( m_vecDesiredFaceDir, FIELD_VECTOR ), + DEFINE_FIELD( m_flLastSeen, FIELD_TIME ), + DEFINE_FIELD( m_flPrevSeen, FIELD_TIME ), +// DEFINE_FIELD( m_iSoundState, FIELD_INTEGER ), // Don't save, precached + DEFINE_FIELD( m_vecTargetPosition, FIELD_POSITION_VECTOR ), + + DEFINE_FIELD( m_hRotorWash, FIELD_EHANDLE ), + + DEFINE_FIELD( m_flMaxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( m_flMaxSpeedFiring, FIELD_FLOAT ), + DEFINE_FIELD( m_flGoalSpeed, FIELD_FLOAT ), + DEFINE_KEYFIELD( m_flInitialSpeed, FIELD_FLOAT, "InitialSpeed" ), + + DEFINE_FIELD( m_flRandomOffsetTime, FIELD_TIME ), + DEFINE_FIELD( m_vecRandomOffset, FIELD_VECTOR ), + DEFINE_FIELD( m_flRotorWashEntitySearchTime, FIELD_TIME ), + DEFINE_FIELD( m_bSuppressSound, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flStartupTime, FIELD_TIME ), + + DEFINE_FIELD( m_cullBoxMins, FIELD_VECTOR ), + DEFINE_FIELD( m_cullBoxMaxs, FIELD_VECTOR ), + + DEFINE_UTLVECTOR( m_hEntitiesPushedByWash, FIELD_EMBEDDED ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate), + DEFINE_INPUTFUNC( FIELD_VOID, "GunOn", InputGunOn ), + DEFINE_INPUTFUNC( FIELD_VOID, "GunOff", InputGunOff ), + DEFINE_INPUTFUNC( FIELD_VOID, "MissileOn", InputMissileOn ), + DEFINE_INPUTFUNC( FIELD_VOID, "MissileOff", InputMissileOff ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnableRotorWash", InputEnableRotorWash ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableRotorWash", InputDisableRotorWash ), + DEFINE_INPUTFUNC( FIELD_VOID, "MoveTopSpeed", InputMoveTopSpeed ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "MoveSpecifiedSpeed", InputMoveSpecifiedSpeed ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetAngles", InputSetAngles ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnableRotorSound", InputEnableRotorSound ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableRotorSound", InputDisableRotorSound ), + DEFINE_INPUTFUNC( FIELD_VOID, "Kill", InputKill ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CBaseHelicopter, DT_BaseHelicopter ) + SendPropTime( SENDINFO( m_flStartupTime ) ), +END_SEND_TABLE() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseHelicopter::CBaseHelicopter( void ) +{ + m_cullBoxMins = vec3_origin; + m_cullBoxMaxs = vec3_origin; + + m_hRotorWash = NULL; +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +// Notes : Have your derived Helicopter's Spawn() function call this one FIRST +//------------------------------------------------------------------------------ +void CBaseHelicopter::Precache( void ) +{ +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +// Notes : Have your derived Helicopter's Spawn() function call this one FIRST +//------------------------------------------------------------------------------ +void CBaseHelicopter::Spawn( void ) +{ + Precache( ); + + SetSolid( SOLID_BBOX ); + SetMoveType( MOVETYPE_STEP ); + AddFlag( FL_FLY ); + SetState( NPC_STATE_IDLE ); + + m_lifeState = LIFE_ALIVE; + + // motor + //****** + // All of this stuff is specific to the individual type of aircraft. Handle it yourself. + //****** + // m_iAmmoType = g_pGameRules->GetAmmoDef()->Index("AR2"); + // SetModel( "models/attack_helicopter.mdl" ); + // UTIL_SetSize( this, Vector( -32, -32, -64 ), Vector( 32, 32, 0 ) ); + // UTIL_SetOrigin( this, GetLocalOrigin() ); + // m_iHealth = 100; + // m_flFieldOfView = -0.707; // 270 degrees + // InitBoneControllers(); + // m_iRockets = 10; + // Get the rotor sound started up. + + // This base class assumes the helicopter has no guns or missiles. + // Set the appropriate flags in your derived class' Spawn() function. + m_fHelicopterFlags &= ~BITS_HELICOPTER_MISSILE_ON; + m_fHelicopterFlags &= ~BITS_HELICOPTER_GUN_ON; + + m_pRotorSound = NULL; + m_pRotorBlast = NULL; + + SetCycle( 0 ); + ResetSequenceInfo(); + + AddFlag( FL_NPC ); + + m_flMaxSpeed = BASECHOPPER_MAX_SPEED; + m_flMaxSpeedFiring = BASECHOPPER_MAX_FIRING_SPEED; + m_takedamage = DAMAGE_AIM; + + // Don't start up if the level designer has asked the + // helicopter to start disabled. + if ( !(m_spawnflags & SF_AWAITINPUT) ) + { + Startup(); + SetNextThink( gpGlobals->curtime + 1.0f ); + } + else + { + m_flStartupTime = FLT_MAX; + } + + InitPathingData( 0, BASECHOPPER_MIN_CHASE_DIST_DIFF, BASECHOPPER_AVOID_DIST ); + + // Setup collision hull + ExpandBBox( m_cullBoxMins, m_cullBoxMaxs ); + CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &m_cullBoxMins, &m_cullBoxMaxs ); + AddSolidFlags( FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST ); + m_flRandomOffsetTime = -1.0f; + m_vecRandomOffset.Init( 0, 0, 0 ); +} + + +//------------------------------------------------------------------------------ +// Cleanup +//------------------------------------------------------------------------------ +void CBaseHelicopter::UpdateOnRemove() +{ + StopRotorWash(); + BaseClass::UpdateOnRemove(); +} + + +//------------------------------------------------------------------------------ +// Gets the max speed of the helicopter +//------------------------------------------------------------------------------ +float CBaseHelicopter::GetMaxSpeed() +{ + // If our last path_track has specified a speed, use that instead of ours + if ( GetPathMaxSpeed() ) + return GetPathMaxSpeed(); + + return m_flMaxSpeed; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CBaseHelicopter::GetMaxSpeedFiring() +{ + // If our last path_track has specified a speed, use that instead of ours + if ( GetPathMaxSpeed() ) + return GetPathMaxSpeed(); + + return m_flMaxSpeedFiring; +} + + +//------------------------------------------------------------------------------ +// Enemy methods +//------------------------------------------------------------------------------ +bool CBaseHelicopter::GetTrackPatherTarget( Vector *pPos ) +{ + if ( GetEnemy() ) + { + *pPos = GetEnemy()->BodyTarget( GetAbsOrigin(), false ); + return true; + } + + return false; +} + +CBaseEntity *CBaseHelicopter::GetTrackPatherTargetEnt() +{ + return GetEnemy(); +} + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +bool CBaseHelicopter::FireGun( void ) +{ + return true; +} + + +//------------------------------------------------------------------------------ +// Purpose : The main think function for the helicopters +// Input : +// Output : +//------------------------------------------------------------------------------ +void CBaseHelicopter::HelicopterThink( void ) +{ + CheckPVSCondition(); + + SetNextThink( gpGlobals->curtime + HELICOPTER_THINK_INTERVAL ); + + // Don't keep this around for more than one frame. + ClearCondition( COND_ENEMY_DEAD ); + + // Animate and dispatch animation events. + StudioFrameAdvance( ); + DispatchAnimEvents( this ); + + PrescheduleThink(); + + if ( IsMarkedForDeletion() ) + return; + + ShowDamage( ); + + // ----------------------------------------------- + // If AI is disabled, kill any motion and return + // ----------------------------------------------- + if (CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI) + { + SetAbsVelocity( vec3_origin ); + SetLocalAngularVelocity( vec3_angle ); + SetNextThink( gpGlobals->curtime + HELICOPTER_THINK_INTERVAL ); + return; + } + + Hunt(); + + // Finally, forget dead enemies, or ones we've been told to ignore. + if( GetEnemy() != NULL && (!GetEnemy()->IsAlive() || GetEnemy()->GetFlags() & FL_NOTARGET || IRelationType( GetEnemy() ) == D_NU ) ) + { + SetEnemy( NULL ); + } + + HelicopterPostThink(); +} + +//----------------------------------------------------------------------------- +// Rotor wash think +//----------------------------------------------------------------------------- +void CBaseHelicopter::RotorWashThink( void ) +{ + if ( m_lifeState == LIFE_ALIVE || m_lifeState == LIFE_DYING ) + { + DrawRotorWash( BASECHOPPER_WASH_ALTITUDE, GetAbsOrigin() ); + SetContextThink( &CBaseHelicopter::RotorWashThink, gpGlobals->curtime + HELICOPTER_ROTORWASH_THINK_INTERVAL, s_pRotorWashThinkContext ); + } + else + { + SetContextThink( NULL, gpGlobals->curtime, s_pRotorWashThinkContext ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseHelicopter::DrawRotorWash( float flAltitude, const Vector &vecRotorOrigin ) +{ + // Shake any ropes nearby + if ( random->RandomInt( 0, 2 ) == 0 ) + { + CRopeKeyframe::ShakeRopes( GetAbsOrigin(), flAltitude, 128 ); + } + + if ( m_spawnflags & SF_NOROTORWASH ) + return; + + DoRotorPhysicsPush( vecRotorOrigin, flAltitude ); + + if ( m_flRotorWashEntitySearchTime > gpGlobals->curtime ) + return; + + // Only push every half second + m_flRotorWashEntitySearchTime = gpGlobals->curtime + 0.5f; +} + + +//----------------------------------------------------------------------------- +// Purpose: Push an airboat in our wash +//----------------------------------------------------------------------------- +#define MAX_AIRBOAT_ROLL_ANGLE 20.0f +#define MAX_AIRBOAT_ROLL_COSANGLE 0.866f +#define MAX_AIRBOAT_ROLL_COSANGLE_X2 0.5f + +void CBaseHelicopter::DoWashPushOnAirboat( CBaseEntity *pAirboat, + const Vector &vecWashToAirboat, float flWashAmount ) +{ + // For the airboat, simply produce a small roll and a push outwards. + // But don't produce a roll if we're too rolled in that direction already. + + // Get the actual up direction vector + Vector vecUp; + pAirboat->GetVectors( NULL, NULL, &vecUp ); + if ( vecUp.z < MAX_AIRBOAT_ROLL_COSANGLE ) + return; + + // Compute roll direction so that we get pushed down on the side where the rotor wash is. + Vector vecRollNormal; + CrossProduct( vecWashToAirboat, Vector( 0, 0, 1 ), vecRollNormal ); + + // Project it into the plane of the roll normal + VectorMA( vecUp, -DotProduct( vecUp, vecRollNormal ), vecRollNormal, vecUp ); + VectorNormalize( vecUp ); + + // Compute a vector which is the max direction we can roll given the roll constraint + Vector vecExtremeUp; + VMatrix rot; + MatrixBuildRotationAboutAxis( rot, vecRollNormal, MAX_AIRBOAT_ROLL_ANGLE ); + MatrixGetColumn( rot, 2, &vecExtremeUp ); + + // Find the angle between how vertical we are and how vertical we should be + float flCosDelta = DotProduct( vecExtremeUp, vecUp ); + float flDelta = acos(flCosDelta) * 180.0f / M_PI; + flDelta = clamp( flDelta, 0.0f, MAX_AIRBOAT_ROLL_ANGLE ); + flDelta = SimpleSplineRemapVal( flDelta, 0.0f, MAX_AIRBOAT_ROLL_ANGLE, 0.0f, 1.0f ); + + float flForce = 12.0f * flWashAmount * flDelta; + + Vector vecWashOrigin; + Vector vecForce; + VectorMultiply( Vector( 0, 0, -1 ), flForce, vecForce ); + VectorMA( pAirboat->GetAbsOrigin(), -200.0f, vecWashToAirboat, vecWashOrigin ); + + pAirboat->VPhysicsTakeDamage( CTakeDamageInfo( this, this, vecForce, vecWashOrigin, flWashAmount, DMG_BLAST ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Push a physics object in our wash. Return false if it's now out of our wash +//----------------------------------------------------------------------------- +bool CBaseHelicopter::DoWashPush( washentity_t *pWash, const Vector &vecWashOrigin ) +{ + if ( !pWash || !pWash->hEntity.Get() ) + return false; + + // Make sure the entity is still within our wash's radius + CBaseEntity *pEntity = pWash->hEntity; + + // This can happen because we can dynamically turn this flag on and off + if ( pEntity->IsEFlagSet( EFL_NO_ROTORWASH_PUSH )) + return false; + + Vector vecSpot = pEntity->BodyTarget( vecWashOrigin ); + Vector vecToSpot = ( vecSpot - vecWashOrigin ); + vecToSpot.z = 0; + float flDist = VectorNormalize( vecToSpot ); + if ( flDist > BASECHOPPER_WASH_RADIUS ) + return false; + + IRotorWashShooter *pShooter = GetRotorWashShooter( pEntity ); + IPhysicsObject *pPhysObject; + + + float flPushTime = (gpGlobals->curtime - pWash->flWashStartTime); + flPushTime = clamp( flPushTime, 0, BASECHOPPER_WASH_RAMP_TIME ); + float flWashAmount = RemapVal( flPushTime, 0, BASECHOPPER_WASH_RAMP_TIME, BASECHOPPER_WASH_PUSH_MIN, BASECHOPPER_WASH_PUSH_MAX ); + + if ( pShooter ) + { + Vector vecForce = (0.015f / 0.1f) * flWashAmount * vecToSpot * phys_pushscale.GetFloat(); + pEntity = pShooter->DoWashPush( pWash->flWashStartTime, vecForce ); + if ( !pEntity ) + return true; + + washentity_t Wash; + Wash.hEntity = pEntity; + Wash.flWashStartTime = pWash->flWashStartTime; + int i = m_hEntitiesPushedByWash.AddToTail( Wash ); + pWash = &m_hEntitiesPushedByWash[i]; + + pPhysObject = pEntity->VPhysicsGetObject(); + if ( !pPhysObject ) + return true; + } + else + { + // Airboat gets special treatment + if ( FClassnameIs( pEntity, "prop_vehicle_airboat" ) ) + { + DoWashPushOnAirboat( pEntity, vecToSpot, flWashAmount ); + return true; + } + + pPhysObject = pEntity->VPhysicsGetObject(); + if ( !pPhysObject ) + return false; + } + + // Push it away from the center of the wash + float flMass = pPhysObject->GetMass(); + + // This used to be mass independent, which is a bad idea because it blows 200kg engine blocks + // as much as it blows cardboard and soda cans. Make this force mass-independent, but clamp at + // 30kg. + flMass = MIN( flMass, 30.0f ); + + Vector vecForce = (0.015f / 0.1f) * flWashAmount * flMass * vecToSpot * phys_pushscale.GetFloat(); + pEntity->VPhysicsTakeDamage( CTakeDamageInfo( this, this, vecForce, vecWashOrigin, flWashAmount, DMG_BLAST ) ); + + // Debug + if ( g_debug_basehelicopter.GetInt() == BASECHOPPER_DEBUG_WASH ) + { + NDebugOverlay::Cross3D( pEntity->GetAbsOrigin(), -Vector(4,4,4), Vector(4,4,4), 255, 0, 0, true, 0.1f ); + NDebugOverlay::Line( pEntity->GetAbsOrigin(), pEntity->GetAbsOrigin() + vecForce, 255, 255, 0, true, 0.1f ); + + IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject(); + Msg("Pushed %s (index %d) (mass %f) with force %f (min %.2f max %.2f) at time %.2f\n", + pEntity->GetClassname(), pEntity->entindex(), pPhysObject->GetMass(), flWashAmount, + BASECHOPPER_WASH_PUSH_MIN * flMass, BASECHOPPER_WASH_PUSH_MAX * flMass, gpGlobals->curtime ); + } + + // If we've pushed this thing for some time, remove it to give us a chance to find lighter things nearby + if ( flPushTime > 2.0 ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseHelicopter::DoRotorPhysicsPush( const Vector &vecRotorOrigin, float flAltitude ) +{ + CBaseEntity *pEntity = NULL; + trace_t tr; + + // First, trace down and find out where the was is hitting the ground + UTIL_TraceLine( vecRotorOrigin, vecRotorOrigin+Vector(0,0,-flAltitude), (MASK_SOLID_BRUSHONLY|CONTENTS_WATER), NULL, COLLISION_GROUP_NONE, &tr ); + // Always raise the physics origin a bit + Vector vecPhysicsOrigin = tr.endpos + Vector(0,0,64); + + // Debug + if ( g_debug_basehelicopter.GetInt() == BASECHOPPER_DEBUG_WASH ) + { + NDebugOverlay::Cross3D( vecPhysicsOrigin, -Vector(16,16,16), Vector(16,16,16), 0, 255, 255, true, 0.1f ); + } + + // Push entities that we've pushed before, and are still within range + // Walk backwards because they may be removed if they're now out of range + int iCount = m_hEntitiesPushedByWash.Count(); + bool bWasPushingObjects = (iCount > 0); + for ( int i = (iCount-1); i >= 0; i-- ) + { + if ( !DoWashPush( &(m_hEntitiesPushedByWash[i]), vecPhysicsOrigin ) ) + { + // Out of range now, so remove + m_hEntitiesPushedByWash.Remove(i); + } + } + + if ( m_flRotorWashEntitySearchTime > gpGlobals->curtime ) + return; + + // Any spare slots? + iCount = m_hEntitiesPushedByWash.Count(); + if ( iCount >= BASECHOPPER_WASH_MAX_OBJECTS ) + return; + + // Find the lightest physics entity below us and add it to our list to push around + CBaseEntity *pLightestEntity = NULL; + float flLightestMass = 9999; + while ((pEntity = gEntList.FindEntityInSphere(pEntity, vecPhysicsOrigin, BASECHOPPER_WASH_RADIUS )) != NULL) + { + IRotorWashShooter *pShooter = GetRotorWashShooter( pEntity ); + + if ( pEntity->IsEFlagSet( EFL_NO_ROTORWASH_PUSH )) + continue; + + if ( pShooter || pEntity->GetMoveType() == MOVETYPE_VPHYSICS || (pEntity->VPhysicsGetObject() && !pEntity->IsPlayer()) ) + { + // Make sure it's not already in our wash + bool bAlreadyPushing = false; + for ( int i = 0; i < iCount; i++ ) + { + if ( m_hEntitiesPushedByWash[i].hEntity == pEntity ) + { + bAlreadyPushing = true; + break; + } + } + if ( bAlreadyPushing ) + continue; + + float flMass = FLT_MAX; + if ( pShooter ) + { + flMass = 1.0f; + } + else + { + // Don't try to push anything too big + IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject(); + if ( pPhysObject ) + { + flMass = pPhysObject->GetMass(); + if ( flMass > BASECHOPPER_WASH_MAX_MASS ) + continue; + } + } + + // Ignore anything bigger than the one we've already found + if ( flMass > flLightestMass ) + continue; + + Vector vecSpot = pEntity->BodyTarget( vecPhysicsOrigin ); + + // Don't push things too far below our starting point (helps reduce through-roof cases w/o doing a trace) + if ( fabs( vecSpot.z - vecPhysicsOrigin.z ) > 96 ) + continue; + + Vector vecToSpot = ( vecSpot - vecPhysicsOrigin ); + vecToSpot.z = 0; + float flDist = VectorNormalize( vecToSpot ); + if ( flDist > BASECHOPPER_WASH_RADIUS ) + continue; + + + // Try to cast to the helicopter; if we can't, then we can't be hit. + if ( pEntity->GetServerVehicle() ) + { + UTIL_TraceLine( vecSpot, vecPhysicsOrigin, MASK_SOLID_BRUSHONLY, pEntity, COLLISION_GROUP_NONE, &tr ); + if ( tr.fraction != 1.0f ) + continue; + } + + flLightestMass = flMass; + pLightestEntity = pEntity; + + washentity_t Wash; + Wash.hEntity = pLightestEntity; + Wash.flWashStartTime = gpGlobals->curtime; + m_hEntitiesPushedByWash.AddToTail( Wash ); + + // Can we fit more after adding this one? No? Then we are done. + iCount = m_hEntitiesPushedByWash.Count(); + if ( iCount >= BASECHOPPER_WASH_MAX_OBJECTS ) + break; + } + } + + // Handle sound. + // If we just started pushing objects, ramp the blast sound up. + if ( !bWasPushingObjects && m_hEntitiesPushedByWash.Count() ) + { + if ( m_pRotorBlast ) + { + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + controller.SoundChangeVolume( m_pRotorBlast, 1.0, 1.0 ); + } + } + else if ( bWasPushingObjects && m_hEntitiesPushedByWash.Count() == 0 ) + { + if ( m_pRotorBlast ) + { + // We just stopped pushing objects, so fade the blast sound out. + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + controller.SoundChangeVolume( m_pRotorBlast, 0, 1.0 ); + } + } +} + + +//------------------------------------------------------------------------------ +// Updates the enemy +//------------------------------------------------------------------------------ +float CBaseHelicopter::EnemySearchDistance( ) +{ + return 4092; +} + + +//------------------------------------------------------------------------------ +// Updates the enemy +//------------------------------------------------------------------------------ +void CBaseHelicopter::UpdateEnemy() +{ + if( HasCondition( COND_ENEMY_DEAD ) ) + { + SetEnemy( NULL ); + } + + // Look for my best enemy. If I change enemies, + // be sure and change my prevseen/lastseen timers. + if( m_lifeState == LIFE_ALIVE ) + { + GetSenses()->Look( EnemySearchDistance() ); + + GetEnemies()->RefreshMemories(); + ChooseEnemy(); + + if( HasEnemy() ) + { + CBaseEntity *pEnemy = GetEnemy(); + GatherEnemyConditions( pEnemy ); + if ( FVisible( pEnemy ) ) + { + if (m_flLastSeen < gpGlobals->curtime - 2) + { + m_flPrevSeen = gpGlobals->curtime; + } + + m_flLastSeen = gpGlobals->curtime; + m_vecTargetPosition = pEnemy->WorldSpaceCenter(); + } + } + else + { + // look at where we're going instead + m_vecTargetPosition = GetDesiredPosition(); + } + } + else + { + // If we're dead or dying, forget our enemy and don't look for new ones(sjb) + SetEnemy( NULL ); + } + +} + +//------------------------------------------------------------------------------ +// Purpose : Override the desired position if your derived helicopter is doing something special +//------------------------------------------------------------------------------ +void CBaseHelicopter::UpdateDesiredPosition( void ) +{ +} + +//------------------------------------------------------------------------------ +// Updates the facing direction +//------------------------------------------------------------------------------ +void CBaseHelicopter::UpdateFacingDirection() +{ + if ( 1 ) + { + Vector targetDir = m_vecTargetPosition - GetAbsOrigin(); + Vector desiredDir = GetDesiredPosition() - GetAbsOrigin(); + + VectorNormalize( targetDir ); + VectorNormalize( desiredDir ); + + if ( !IsCrashing() && m_flLastSeen + 5 > gpGlobals->curtime ) //&& DotProduct( targetDir, desiredDir) > 0.25) + { + // If we've seen the target recently, face the target. + //Msg( "Facing Target \n" ); + m_vecDesiredFaceDir = targetDir; + } + else + { + // Face our desired position. + // Msg( "Facing Position\n" ); + m_vecDesiredFaceDir = desiredDir; + } + } + else + { + // Face the way the path corner tells us to. + //Msg( "Facing my path corner\n" ); + m_vecDesiredFaceDir = GetGoalOrientation(); + } + +} + + +//------------------------------------------------------------------------------ +// Fire weapons +//------------------------------------------------------------------------------ +void CBaseHelicopter::FireWeapons() +{ + // ALERT( at_console, "%.0f %.0f %.0f\n", gpGlobals->curtime, m_flLastSeen, m_flPrevSeen ); + if (m_fHelicopterFlags & BITS_HELICOPTER_GUN_ON) + { + //if ( (m_flLastSeen + 1 > gpGlobals->curtime) && (m_flPrevSeen + 2 < gpGlobals->curtime) ) + { + if (FireGun( )) + { + // slow down if we're firing + if (m_flGoalSpeed > GetMaxSpeedFiring() ) + { + m_flGoalSpeed = GetMaxSpeedFiring(); + } + } + } + } + + if (m_fHelicopterFlags & BITS_HELICOPTER_MISSILE_ON) + { + AimRocketGun(); + } +} + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void CBaseHelicopter::Hunt( void ) +{ + UpdateEnemy(); + + UpdateTrackNavigation( ); + + UpdateDesiredPosition(); + + UpdateFacingDirection(); + + Flight(); + + UpdatePlayerDopplerShift( ); + + FireWeapons(); +} + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void CBaseHelicopter::UpdatePlayerDopplerShift( ) +{ + // ----------------------------- + // make rotor, engine sounds + // ----------------------------- + if (m_iSoundState == 0) + { + // Sound startup. + InitializeRotorSound(); + } + else + { + CBaseEntity *pPlayer = NULL; + + // UNDONE: this needs to send different sounds to every player for multiplayer. + // FIXME: this isn't the correct way to find a player!!! + pPlayer = gEntList.FindEntityByName( NULL, "!player" ); + if (pPlayer) + { + Vector dir; + VectorSubtract( pPlayer->GetAbsOrigin(), GetAbsOrigin(), dir ); + VectorNormalize(dir); + +#if 1 + float velReceiver = DotProduct( pPlayer->GetAbsVelocity(), dir ); + float velTransmitter = -DotProduct( GetAbsVelocity(), dir ); + // speed of sound == 13049in/s + int iPitch = 100 * ((1 - velReceiver / 13049) / (1 + velTransmitter / 13049)); +#else + // This is a bogus doppler shift, but I like it better + float relV = DotProduct( GetAbsVelocity() - pPlayer->GetAbsVelocity(), dir ); + int iPitch = (int)(100 + relV / 50.0); +#endif + + // clamp pitch shifts + if (iPitch > 250) + { + iPitch = 250; + } + if (iPitch < 50) + { + iPitch = 50; + } + + UpdateRotorSoundPitch( iPitch ); + // Msg( "Pitch:%d\n", iPitch ); + } + else + { + Msg( "Chopper didn't find a player!\n" ); + } + } +} + + +//----------------------------------------------------------------------------- +// Computes the actual position to fly to +//----------------------------------------------------------------------------- +void CBaseHelicopter::ComputeActualTargetPosition( float flSpeed, float flTime, float flPerpDist, Vector *pDest, bool bApplyNoise ) +{ + // This is used to make the helicopter drift around a bit. + if ( bApplyNoise && m_flRandomOffsetTime <= gpGlobals->curtime ) + { + m_vecRandomOffset.Random( -25.0f, 25.0f ); + m_flRandomOffsetTime = gpGlobals->curtime + 1.0f; + } + + if ( IsLeading() && GetEnemy() && IsOnPathTrack() ) + { + ComputePointAlongCurrentPath( flSpeed * flTime, flPerpDist, pDest ); + *pDest += m_vecRandomOffset; + return; + } + + *pDest = GetDesiredPosition() - GetAbsOrigin(); + float flDistToDesired = pDest->Length(); + if (flDistToDesired > flSpeed * flTime) + { + float scale = flSpeed * flTime / flDistToDesired; + *pDest *= scale; + } + else if ( IsOnPathTrack() ) + { + // Blend in a fake destination point based on the dest velocity + Vector vecDestVelocity; + ComputeNormalizedDestVelocity( &vecDestVelocity ); + vecDestVelocity *= flSpeed; + + float flBlendFactor = 1.0f - flDistToDesired / (flSpeed * flTime); + VectorMA( *pDest, flTime * flBlendFactor, vecDestVelocity, *pDest ); + } + + *pDest += GetAbsOrigin(); + + if ( bApplyNoise ) + { + // ComputePointAlongCurrentPath( flSpeed * flTime, flPerpDist, pDest ); + *pDest += m_vecRandomOffset; + } +} + + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +void CBaseHelicopter::Flight( void ) +{ + if( GetFlags() & FL_ONGROUND ) + { + //This would be really bad. + SetGroundEntity( NULL ); + } + + // Generic speed up + if (m_flGoalSpeed < GetMaxSpeed()) + { + m_flGoalSpeed += GetAcceleration(); + } + + //NDebugOverlay::Line(GetAbsOrigin(), m_vecDesiredPosition, 0,0,255, true, 0.1); + + // tilt model 5 degrees (why?! sjb) + QAngle vecAdj = QAngle( 5.0, 0, 0 ); + + // estimate where I'll be facing in one seconds + Vector forward, right, up; + AngleVectors( GetLocalAngles() + GetLocalAngularVelocity() * 2 + vecAdj, &forward, &right, &up ); + + // Vector vecEst1 = GetLocalOrigin() + GetAbsVelocity() + up * m_flForce - Vector( 0, 0, 384 ); + // float flSide = DotProduct( m_vecDesiredPosition - vecEst1, right ); + QAngle angVel = GetLocalAngularVelocity(); + float flSide = DotProduct( m_vecDesiredFaceDir, right ); + if (flSide < 0) + { + if (angVel.y < 60) + { + angVel.y += 8; + } + } + else + { + if (angVel.y > -60) + { + angVel.y -= 8; + } + } + + angVel.y *= ( 0.98 ); // why?! (sjb) + + // estimate where I'll be in two seconds + AngleVectors( GetLocalAngles() + angVel * 1 + vecAdj, NULL, NULL, &up ); + Vector vecEst = GetAbsOrigin() + GetAbsVelocity() * 2.0 + up * m_flForce * 20 - Vector( 0, 0, 384 * 2 ); + + // add immediate force + AngleVectors( GetLocalAngles() + vecAdj, &forward, &right, &up ); + + Vector vecImpulse( 0, 0, 0 ); + vecImpulse.x += up.x * m_flForce; + vecImpulse.y += up.y * m_flForce; + vecImpulse.z += up.z * m_flForce; + + // add gravity + vecImpulse.z -= 38.4; // 32ft/sec + ApplyAbsVelocityImpulse( vecImpulse ); + + float flSpeed = GetAbsVelocity().Length(); + float flDir = DotProduct( Vector( forward.x, forward.y, 0 ), Vector( GetAbsVelocity().x, GetAbsVelocity().y, 0 ) ); + if (flDir < 0) + { + flSpeed = -flSpeed; + } + + float flDist = DotProduct( GetDesiredPosition() - vecEst, forward ); + + // float flSlip = DotProduct( GetAbsVelocity(), right ); + float flSlip = -DotProduct( GetDesiredPosition() - vecEst, right ); + + // fly sideways + if (flSlip > 0) + { + if (GetLocalAngles().z > -30 && angVel.z > -15) + angVel.z -= 4; + else + angVel.z += 2; + } + else + { + if (GetLocalAngles().z < 30 && angVel.z < 15) + angVel.z += 4; + else + angVel.z -= 2; + } + + // These functions contain code Ken wrote that used to be right here as part of the flight model, + // but we want different helicopter vehicles to have different drag characteristics, so I made + // them virtual functions (sjb) + ApplySidewaysDrag( right ); + ApplyGeneralDrag(); + + // apply power to stay correct height + // FIXME: these need to be per class variables +#define MAX_FORCE 80 +#define FORCE_POSDELTA 12 +#define FORCE_NEGDELTA 8 + + if (m_flForce < MAX_FORCE && vecEst.z < GetDesiredPosition().z) + { + m_flForce += FORCE_POSDELTA; + } + else if (m_flForce > 30) + { + if (vecEst.z > GetDesiredPosition().z) + m_flForce -= FORCE_NEGDELTA; + } + + // pitch forward or back to get to target + //----------------------------------------- + // Pitch is reversed since Half-Life! (sjb) + //----------------------------------------- + if (flDist > 0 && flSpeed < m_flGoalSpeed /* && flSpeed < flDist */ && GetLocalAngles().x + angVel.x < 40) + { + // ALERT( at_console, "F " ); + // lean forward + angVel.x += 12.0; + } + else if (flDist < 0 && flSpeed > -50 && GetLocalAngles().x + angVel.x > -20) + { + // ALERT( at_console, "B " ); + // lean backward + angVel.x -= 12.0; + } + else if (GetLocalAngles().x + angVel.x < 0) + { + // ALERT( at_console, "f " ); + angVel.x += 4.0; + } + else if (GetLocalAngles().x + angVel.x > 0) + { + // ALERT( at_console, "b " ); + angVel.x -= 4.0; + } + + SetLocalAngularVelocity( angVel ); + // ALERT( at_console, "%.0f %.0f : %.0f %.0f : %.0f %.0f : %.0f\n", GetAbsOrigin().x, GetAbsVelocity().x, flDist, flSpeed, GetLocalAngles().x, m_vecAngVelocity.x, m_flForce ); + // ALERT( at_console, "%.0f %.0f : %.0f %0.f : %.0f\n", GetAbsOrigin().z, GetAbsVelocity().z, vecEst.z, m_vecDesiredPosition.z, m_flForce ); +} + + +//------------------------------------------------------------------------------ +// Updates the rotor wash volume +//------------------------------------------------------------------------------ +void CBaseHelicopter::UpdateRotorWashVolume() +{ + if ( !m_pRotorSound ) + return; + + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + float flVolDelta = GetRotorVolume() - controller.SoundGetVolume( m_pRotorSound ); + if ( flVolDelta ) + { + // We can change from 0 to 1 in 3 seconds. + // Figure out how many seconds flVolDelta will take. + float flRampTime = fabs( flVolDelta ) * 3.0f; + controller.SoundChangeVolume( m_pRotorSound, GetRotorVolume(), flRampTime ); + } +} + + +//------------------------------------------------------------------------------ +// For scripted times where it *has* to shoot +//------------------------------------------------------------------------------ +float CBaseHelicopter::GetRotorVolume( void ) +{ + return m_bSuppressSound ? 0.0f : 1.0f; +} + + +//----------------------------------------------------------------------------- +// Rotor sound +//----------------------------------------------------------------------------- +void CBaseHelicopter::InputEnableRotorSound( inputdata_t &inputdata ) +{ + m_bSuppressSound = false; +} + +void CBaseHelicopter::InputDisableRotorSound( inputdata_t &inputdata ) +{ + m_bSuppressSound = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Marks the entity for deletion +//----------------------------------------------------------------------------- +void CBaseHelicopter::InputKill( inputdata_t &inputdata ) +{ + StopRotorWash(); + + m_bSuppressSound = true; + SetContextThink( &CBaseHelicopter::DelayedKillThink, gpGlobals->curtime + 3.0f, s_pDelayedKillThinkContext ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseHelicopter::StopRotorWash( void ) +{ + if ( m_hRotorWash ) + { + UTIL_Remove( m_hRotorWash ); + m_hRotorWash = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Marks the entity for deletion +//----------------------------------------------------------------------------- +void CBaseHelicopter::DelayedKillThink( ) +{ + // tell owner ( if any ) that we're dead.This is mostly for NPCMaker functionality. + CBaseEntity *pOwner = GetOwnerEntity(); + if ( pOwner ) + { + pOwner->DeathNotice( this ); + SetOwnerEntity( NULL ); + } + + UTIL_Remove( this ); +} + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void CBaseHelicopter::InitializeRotorSound( void ) +{ + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + + if ( m_pRotorSound ) + { + // Get the rotor sound started up. + controller.Play( m_pRotorSound, 0.0, 100 ); + UpdateRotorWashVolume(); + } + + if ( m_pRotorBlast ) + { + // Start the blast sound and then immediately drop it to 0 (starting it at 0 wouldn't start it) + controller.Play( m_pRotorBlast, 1.0, 100 ); + controller.SoundChangeVolume(m_pRotorBlast, 0, 0.0); + } + + m_iSoundState = SND_CHANGE_PITCH; // hack for going through level transitions +} + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void CBaseHelicopter::UpdateRotorSoundPitch( int iPitch ) +{ + if (m_pRotorSound) + { + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + controller.SoundChangePitch( m_pRotorSound, iPitch, 0.1 ); + UpdateRotorWashVolume(); + } +} + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void CBaseHelicopter::FlyTouch( CBaseEntity *pOther ) +{ + // bounce if we hit something solid + if ( pOther->GetSolid() == SOLID_BSP) + { +// trace_t tr; +// tr = CBaseEntity::GetTouchTrace(); + + // UNDONE, do a real bounce + // FIXME: This causes bad problems, so we just ignore it right now + //ApplyAbsVelocityImpulse( tr.plane.normal * (GetAbsVelocity().Length() + 200) ); + } +} + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void CBaseHelicopter::CrashTouch( CBaseEntity *pOther ) +{ + // only crash if we hit something solid + if ( pOther->GetSolid() == SOLID_BSP) + { + SetTouch( NULL ); + SetNextThink( gpGlobals->curtime ); + } +} + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void CBaseHelicopter::DyingThink( void ) +{ + StudioFrameAdvance( ); + SetNextThink( gpGlobals->curtime + 0.1f ); + + SetLocalAngularVelocity( GetLocalAngularVelocity() * 1.02 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Override base class to add display of fly direction +// Input : +// Output : +//----------------------------------------------------------------------------- +void CBaseHelicopter::DrawDebugGeometryOverlays(void) +{ + if (m_pfnThink!= NULL) + { + // ------------------------------ + // Draw route if requested + // ------------------------------ + if (m_debugOverlays & OVERLAY_NPC_ROUTE_BIT) + { + NDebugOverlay::Line(GetAbsOrigin(), GetDesiredPosition(), 0,0,255, true, 0); + } + } + BaseClass::DrawDebugGeometryOverlays(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CBaseHelicopter::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) +{ + // Take no damage from trace attacks unless it's blast damage. RadiusDamage() sometimes calls + // TraceAttack() as a means for delivering blast damage. Usually when the explosive penetrates + // the target. (RPG missiles do this sometimes). + if( info.GetDamageType() & (DMG_BLAST|DMG_AIRBOAT) ) + { + BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator ); + } +} + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void CBaseHelicopter::NullThink( void ) +{ + StudioFrameAdvance( ); + SetNextThink( gpGlobals->curtime + 0.5f ); +} + + +void CBaseHelicopter::Startup( void ) +{ + StopRotorWash(); + + if ( !( m_spawnflags & SF_NOROTORWASH ) ) + { + m_hRotorWash = CreateRotorWashEmitter( GetAbsOrigin(), GetAbsAngles(), this, BASECHOPPER_WASH_ALTITUDE ); + } + + // Fade in the blades + m_flStartupTime = gpGlobals->curtime; + + m_flGoalSpeed = m_flInitialSpeed; + SetThink( &CBaseHelicopter::HelicopterThink ); + SetTouch( &CBaseHelicopter::FlyTouch ); + SetNextThink( gpGlobals->curtime + 0.1f ); + + m_flRotorWashEntitySearchTime = gpGlobals->curtime; + SetContextThink( &CBaseHelicopter::RotorWashThink, gpGlobals->curtime, s_pRotorWashThinkContext ); +} + +void CBaseHelicopter::StopLoopingSounds() +{ + // Kill the rotor sounds + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + controller.SoundDestroy( m_pRotorSound ); + controller.SoundDestroy( m_pRotorBlast ); + m_pRotorSound = NULL; + m_pRotorBlast = NULL; + + BaseClass::StopLoopingSounds(); +} + +void CBaseHelicopter::Event_Killed( const CTakeDamageInfo &info ) +{ + m_lifeState = LIFE_DYING; + + SetMoveType( MOVETYPE_FLYGRAVITY ); + SetGravity( UTIL_ScaleForGravity( 240 ) ); // use a lower gravity + + StopLoopingSounds(); + + UTIL_SetSize( this, Vector( -32, -32, -64), Vector( 32, 32, 0) ); + SetThink( &CBaseHelicopter::CallDyingThink ); + SetTouch( &CBaseHelicopter::CrashTouch ); + + SetNextThink( gpGlobals->curtime + 0.1f ); + m_iHealth = 0; + m_takedamage = DAMAGE_NO; + +/* + if (m_spawnflags & SF_NOWRECKAGE) + { + m_flNextRocket = gpGlobals->curtime + 4.0; + } + else + { + m_flNextRocket = gpGlobals->curtime + 15.0; + } +*/ + StopRotorWash(); + + m_OnDeath.FireOutput( info.GetAttacker(), this ); +} + + +void CBaseHelicopter::GibMonster( void ) +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: Call Startup for a helicopter that's been flagged to start disabled +//----------------------------------------------------------------------------- +void CBaseHelicopter::InputActivate( inputdata_t &inputdata ) +{ + if( m_spawnflags & SF_AWAITINPUT ) + { + Startup(); + + // Now clear the spawnflag to protect from + // subsequent calls. + m_spawnflags &= ~SF_AWAITINPUT; + } +} + +//------------------------------------------------------------------------------ +// Purpose : Turn the gun on +//------------------------------------------------------------------------------ +void CBaseHelicopter::InputGunOn( inputdata_t &inputdata ) +{ + m_fHelicopterFlags |= BITS_HELICOPTER_GUN_ON; +} + +//----------------------------------------------------------------------------- +// Purpose: Turn the gun off +//----------------------------------------------------------------------------- +void CBaseHelicopter::InputGunOff( inputdata_t &inputdata ) +{ + m_fHelicopterFlags &= ~BITS_HELICOPTER_GUN_ON; +} + +//------------------------------------------------------------------------------ +// Purpose : Turn the missile on +//------------------------------------------------------------------------------ +void CBaseHelicopter::InputMissileOn( inputdata_t &inputdata ) +{ + m_fHelicopterFlags |= BITS_HELICOPTER_MISSILE_ON; +} + +//----------------------------------------------------------------------------- +// Purpose: Turn the missile off +//----------------------------------------------------------------------------- +void CBaseHelicopter::InputMissileOff( inputdata_t &inputdata ) +{ + m_fHelicopterFlags &= ~BITS_HELICOPTER_MISSILE_ON; +} + + +//----------------------------------------------------------------------------- +// Enable, disable rotor wash +//----------------------------------------------------------------------------- +void CBaseHelicopter::InputEnableRotorWash( inputdata_t &inputdata ) +{ + m_spawnflags &= ~SF_NOROTORWASH; +} + +void CBaseHelicopter::InputDisableRotorWash( inputdata_t &inputdata ) +{ + m_spawnflags |= SF_NOROTORWASH; +} + + +//----------------------------------------------------------------------------- +// Causes the helicopter to immediately accelerate to its desired velocity +//----------------------------------------------------------------------------- +void CBaseHelicopter::InputMoveTopSpeed( inputdata_t &inputdata ) +{ + Vector vecVelocity; + ComputeActualTargetPosition( GetMaxSpeed(), 1.0f, 0.0f, &vecVelocity, false ); + vecVelocity -= GetAbsOrigin(); + + float flLength = VectorNormalize( vecVelocity ); + if (flLength < 1e-3) + { + GetVectors( &vecVelocity, NULL, NULL ); + } + + vecVelocity *= GetMaxSpeed(); + SetAbsVelocity( vecVelocity ); +} + +//----------------------------------------------------------------------------- +// Cause helicopter to immediately accelerate to specified velocity +//----------------------------------------------------------------------------- +void CBaseHelicopter::InputMoveSpecifiedSpeed( inputdata_t &inputdata ) +{ + Vector vecVelocity; + ComputeActualTargetPosition( GetMaxSpeed(), 1.0f, 0.0f, &vecVelocity, false ); + vecVelocity -= GetAbsOrigin(); + + float flLength = VectorNormalize( vecVelocity ); + if (flLength < 1e-3) + { + GetVectors( &vecVelocity, NULL, NULL ); + } + + float flSpeed = inputdata.value.Float(); + + vecVelocity *= flSpeed; + SetAbsVelocity( vecVelocity ); +} + +//------------------------------------------------------------------------------ +// Input values +//------------------------------------------------------------------------------ +void CBaseHelicopter::InputSetAngles( inputdata_t &inputdata ) +{ + const char *pAngles = inputdata.value.String(); + + QAngle angles; + UTIL_StringToVector( angles.Base(), pAngles ); + SetAbsAngles( angles ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CBaseHelicopter::ApplySidewaysDrag( const Vector &vecRight ) +{ + Vector vecNewVelocity = GetAbsVelocity(); + vecNewVelocity.x *= 1.0 - fabs( vecRight.x ) * 0.05; + vecNewVelocity.y *= 1.0 - fabs( vecRight.y ) * 0.05; + vecNewVelocity.z *= 1.0 - fabs( vecRight.z ) * 0.05; + SetAbsVelocity( vecNewVelocity ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CBaseHelicopter::ApplyGeneralDrag( void ) +{ + Vector vecNewVelocity = GetAbsVelocity(); + vecNewVelocity *= 0.995; + SetAbsVelocity( vecNewVelocity ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CBaseHelicopter::ChooseEnemy( void ) +{ + // See if there's a new enemy. + CBaseEntity *pNewEnemy; + + pNewEnemy = BestEnemy(); + + if ( pNewEnemy != GetEnemy() ) + { + if ( pNewEnemy != NULL ) + { + // New enemy! Clear the timers and set conditions. + SetEnemy( pNewEnemy ); + m_flLastSeen = m_flPrevSeen = gpGlobals->curtime; + } + else + { + SetEnemy( NULL ); + SetState( NPC_STATE_ALERT ); + } + return true; + } + else + { + ClearCondition( COND_NEW_ENEMY ); + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CBaseHelicopter::GatherEnemyConditions( CBaseEntity *pEnemy ) +{ + // ------------------- + // If enemy is dead + // ------------------- + if ( !pEnemy->IsAlive() ) + { + SetCondition( COND_ENEMY_DEAD ); + ClearCondition( COND_SEE_ENEMY ); + ClearCondition( COND_ENEMY_OCCLUDED ); + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pInfo - +// bAlways - +//----------------------------------------------------------------------------- +void CBaseHelicopter::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways ) +{ + // Are we already marked for transmission? + if ( pInfo->m_pTransmitEdict->Get( entindex() ) ) + return; + + BaseClass::SetTransmit( pInfo, bAlways ); + + // Make our smoke trail always come with us + if ( m_hRotorWash ) + { + m_hRotorWash->SetTransmit( pInfo, bAlways ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpandBBox(Vector &vecMins, Vector &vecMaxs) +{ + // expand for *any* rotation + float maxval = 0; + for (int i = 0; i < 3; i++) + { + float v = fabs( vecMins[i]); + if (v > maxval) + maxval = v; + + v = fabs( vecMaxs[i]); + if (v > maxval) + maxval = v; + } + + vecMins.Init(-maxval, -maxval, -maxval); + vecMaxs.Init(maxval, maxval, maxval); +} |