You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1597 lines
46 KiB
1597 lines
46 KiB
//========= 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); |
|
}
|
|
|