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.
1589 lines
39 KiB
1589 lines
39 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//=============================================================================// |
|
#include "cbase.h" |
|
#include "beam_shared.h" |
|
#include "ai_default.h" |
|
#include "ai_task.h" |
|
#include "ai_schedule.h" |
|
#include "ai_node.h" |
|
#include "ai_hull.h" |
|
#include "ai_hint.h" |
|
#include "ai_memory.h" |
|
#include "ai_route.h" |
|
#include "ai_motor.h" |
|
#include "ai_senses.h" |
|
#include "hl1_npc_hgrunt.h" |
|
#include "soundent.h" |
|
#include "game.h" |
|
#include "npcevent.h" |
|
#include "entitylist.h" |
|
#include "activitylist.h" |
|
#include "animation.h" |
|
#include "engine/IEngineSound.h" |
|
#include "ammodef.h" |
|
#include "basecombatweapon.h" |
|
#include "hl1_basegrenade.h" |
|
#include "soundenvelope.h" |
|
#include "hl1_CBaseHelicopter.h" |
|
#include "IEffects.h" |
|
#include "smoke_trail.h" |
|
|
|
extern short g_sModelIndexFireball; |
|
|
|
typedef struct |
|
{ |
|
int isValid; |
|
EHANDLE hGrunt; |
|
Vector vecOrigin; |
|
Vector vecAngles; |
|
} t_ospreygrunt; |
|
|
|
#define LOADED_WITH_GRUNTS 0 //WORST NAME EVER! |
|
#define UNLOADING_GRUNTS 1 |
|
#define GRUNTS_DEPLOYED 2 //Waiting for them to finish repelin |
|
#define SF_WAITFORTRIGGER 0x40 |
|
#define MAX_CARRY 24 |
|
#define DEFAULT_SPEED 250 |
|
#define HELICOPTER_THINK_INTERVAL 0.1 |
|
|
|
class CNPC_Osprey : public CBaseHelicopter |
|
{ |
|
DECLARE_CLASS( CNPC_Osprey, CBaseHelicopter ); |
|
public: |
|
|
|
int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } |
|
|
|
void Spawn( void ); |
|
void Precache( void ); |
|
Class_T Classify( void ) { return CLASS_NONE; }; |
|
int BloodColor( void ) { return DONT_BLEED; } |
|
|
|
void FindAllThink( void ); |
|
void DeployThink( void ); |
|
bool HasDead( void ); |
|
void Flight( void ); |
|
void HoverThink( void ); |
|
CAI_BaseNPC *MakeGrunt( Vector vecSrc ); |
|
|
|
void InitializeRotorSound( void ); |
|
void PrescheduleThink( void ); |
|
|
|
void DyingThink( void ); |
|
|
|
void CrashTouch( CBaseEntity *pOther ); |
|
|
|
/* |
|
|
|
void CrashTouch( CBaseEntity *pOther ); |
|
void DyingThink( void ); |
|
void CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); |
|
*/ |
|
void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ); |
|
|
|
float m_startTime; |
|
|
|
float m_flIdealtilt; |
|
float m_flRotortilt; |
|
|
|
float m_flRightHealth; |
|
float m_flLeftHealth; |
|
|
|
int m_iUnits; |
|
EHANDLE m_hGrunt[MAX_CARRY]; |
|
Vector m_vecOrigin[MAX_CARRY]; |
|
EHANDLE m_hRepel[4]; |
|
|
|
int m_iSoundState; |
|
int m_iSpriteTexture; |
|
|
|
int m_iPitch; |
|
|
|
int m_iExplode; |
|
int m_iTailGibs; |
|
int m_iBodyGibs; |
|
int m_iEngineGibs; |
|
|
|
int m_iDoLeftSmokePuff; |
|
int m_iDoRightSmokePuff; |
|
|
|
int m_iRepelState; |
|
float m_flPrevGoalVel; |
|
|
|
int m_iRotorAngle; |
|
int m_nDebrisModel; |
|
|
|
CHandle<SmokeTrail> m_hLeftSmoke; |
|
CHandle<SmokeTrail> m_hRightSmoke; |
|
|
|
DECLARE_DATADESC(); |
|
|
|
int m_iNextCrashModel; //which gib to explode with next |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( monster_osprey, CNPC_Osprey ); |
|
|
|
BEGIN_DATADESC( CNPC_Osprey ) |
|
DEFINE_FIELD( m_startTime, FIELD_TIME ), |
|
|
|
DEFINE_FIELD( m_flIdealtilt, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flRotortilt, FIELD_FLOAT ), |
|
|
|
DEFINE_FIELD( m_flRightHealth, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flLeftHealth, FIELD_FLOAT ), |
|
|
|
DEFINE_FIELD( m_iRepelState, FIELD_INTEGER ), |
|
|
|
DEFINE_FIELD( m_iUnits, FIELD_INTEGER ), |
|
DEFINE_ARRAY( m_hGrunt, FIELD_EHANDLE, MAX_CARRY ), |
|
DEFINE_ARRAY( m_vecOrigin, FIELD_POSITION_VECTOR, MAX_CARRY ), |
|
DEFINE_ARRAY( m_hRepel, FIELD_EHANDLE, 4 ), |
|
|
|
// DEFINE_FIELD( m_iTailGibs, FIELD_INTEGER ), |
|
// DEFINE_FIELD( m_iBodyGibs, FIELD_INTEGER ), |
|
// DEFINE_FIELD( m_iEngineGibs, FIELD_INTEGER ), |
|
// DEFINE_FIELD( m_nDebrisModel, FIELD_INTEGER ), |
|
|
|
// DEFINE_FIELD( m_iSoundState, FIELD_INTEGER ), |
|
// DEFINE_FIELD( m_iSpriteTexture, FIELD_INTEGER ), |
|
// DEFINE_FIELD( m_iPitch, FIELD_INTEGER ), |
|
|
|
DEFINE_FIELD( m_flPrevGoalVel, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_iRotorAngle, FIELD_INTEGER ), |
|
|
|
DEFINE_FIELD( m_hLeftSmoke, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_hRightSmoke, FIELD_EHANDLE ), |
|
|
|
DEFINE_FIELD( m_iNextCrashModel, FIELD_INTEGER ), |
|
|
|
DEFINE_FIELD( m_iDoLeftSmokePuff, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_iDoRightSmokePuff, FIELD_INTEGER ), |
|
|
|
//DEFINE_FIELD( m_iExplode, FIELD_INTEGER ), |
|
|
|
DEFINE_THINKFUNC( FindAllThink ), |
|
DEFINE_THINKFUNC( DeployThink ), |
|
|
|
DEFINE_ENTITYFUNC( CrashTouch ), |
|
|
|
/* DEFINE_FUNCTION ( HoverThink ), |
|
DEFINE_FUNCTION ( DyingThink ), |
|
DEFINE_FUNCTION ( CommandUse ),*/ |
|
END_DATADESC() |
|
|
|
|
|
void CNPC_Osprey::Spawn( void ) |
|
{ |
|
Precache( ); |
|
// motor |
|
SetModel( "models/osprey.mdl" ); |
|
|
|
BaseClass::Spawn(); |
|
|
|
Vector mins, maxs; |
|
ExtractBbox( 0, mins, maxs ); |
|
UTIL_SetSize( this, mins, maxs ); |
|
UTIL_SetOrigin( this, GetAbsOrigin() ); |
|
|
|
AddFlag( FL_NPC ); |
|
m_takedamage = DAMAGE_YES; |
|
m_flRightHealth = 200; |
|
m_flLeftHealth = 200; |
|
m_iHealth = 400; |
|
|
|
m_flFieldOfView = 0; // 180 degrees |
|
|
|
SetSequence( 0 ); |
|
ResetSequenceInfo( ); |
|
SetCycle( random->RandomInt( 0,0xFF ) ); |
|
|
|
// InitBoneControllers(); |
|
|
|
m_startTime = gpGlobals->curtime + 1; |
|
|
|
//FindAllThink(); |
|
// SetUse( CommandUse ); |
|
|
|
/* if (!( m_spawnflags & SF_WAITFORTRIGGER)) |
|
{ |
|
SetThink( gpGlobals->curtime + 1.0 ); |
|
}*/ |
|
|
|
m_flMaxSpeed = (float)BASECHOPPER_MAX_SPEED / 2; |
|
|
|
m_iRepelState = LOADED_WITH_GRUNTS; |
|
m_flPrevGoalVel = 9999; |
|
|
|
m_iRotorAngle = -1; |
|
SetBoneController( 0, m_iRotorAngle ); |
|
|
|
m_hLeftSmoke = NULL; |
|
m_hRightSmoke = NULL; |
|
} |
|
|
|
|
|
void CNPC_Osprey::Precache( void ) |
|
{ |
|
UTIL_PrecacheOther( "monster_human_grunt" ); |
|
|
|
PrecacheModel("models/osprey.mdl"); |
|
PrecacheModel("models/HVR.mdl"); |
|
|
|
m_iSpriteTexture = PrecacheModel( "sprites/rope.vmt" ); |
|
|
|
m_iExplode = PrecacheModel( "sprites/fexplo.vmt" ); |
|
m_iTailGibs = PrecacheModel( "models/osprey_tailgibs.mdl" ); |
|
m_iBodyGibs = PrecacheModel( "models/osprey_bodygibs.mdl" ); |
|
m_iEngineGibs = PrecacheModel( "models/osprey_enginegibs.mdl" ); |
|
|
|
m_nDebrisModel = PrecacheModel( "models/mechgibs.mdl" ); |
|
|
|
PrecacheScriptSound( "Apache.RotorSpinup" ); |
|
|
|
BaseClass::Precache(); |
|
} |
|
|
|
void CNPC_Osprey::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) |
|
{ |
|
float flDamage = info.GetDamage(); |
|
|
|
// Hit right engine |
|
if (ptr->hitgroup == 3 ) |
|
{ |
|
if( m_flRightHealth <= 0 ) |
|
return; |
|
else |
|
m_flRightHealth -= flDamage; |
|
|
|
if( m_flRightHealth <= 0 ) |
|
{ |
|
Assert( m_hRightSmoke == NULL ); |
|
|
|
if ( (m_hRightSmoke = SmokeTrail::CreateSmokeTrail()) != NULL ) |
|
{ |
|
m_hRightSmoke->m_Opacity = 1.0f; |
|
m_hRightSmoke->m_SpawnRate = 60; |
|
m_hRightSmoke->m_ParticleLifetime = 1.3f; |
|
m_hRightSmoke->m_StartColor.Init( 0.65f, 0.65f , 0.65f ); |
|
m_hRightSmoke->m_EndColor.Init( 0.65f, 0.65f, 0.65f ); |
|
m_hRightSmoke->m_StartSize = 12; |
|
m_hRightSmoke->m_EndSize = 80; |
|
m_hRightSmoke->m_SpawnRadius = 8; |
|
m_hRightSmoke->m_MinSpeed = 2; |
|
m_hRightSmoke->m_MaxSpeed = 24; |
|
|
|
m_hRightSmoke->SetLifetime( 1e6 ); |
|
m_hRightSmoke->FollowEntity( this, "right" ); |
|
} |
|
} |
|
} |
|
|
|
// Hit left engine |
|
if (ptr->hitgroup == 2 ) |
|
{ |
|
if( m_flLeftHealth <= 0 ) |
|
return; |
|
else |
|
m_flLeftHealth -= flDamage; |
|
|
|
if( m_flLeftHealth <= 0 ) |
|
{ |
|
//create smoke trail |
|
Assert( m_hLeftSmoke == NULL ); |
|
|
|
if ( (m_hLeftSmoke = SmokeTrail::CreateSmokeTrail()) != NULL ) |
|
{ |
|
m_hLeftSmoke->m_Opacity = 1.0f; |
|
m_hLeftSmoke->m_SpawnRate = 60; |
|
m_hLeftSmoke->m_ParticleLifetime = 1.3f; |
|
m_hLeftSmoke->m_StartColor.Init( 0.65f, 0.65f , 0.65f ); |
|
m_hLeftSmoke->m_EndColor.Init( 0.65f, 0.65f, 0.65f ); |
|
m_hLeftSmoke->m_StartSize = 12; |
|
m_hLeftSmoke->m_EndSize = 64; |
|
m_hLeftSmoke->m_SpawnRadius = 8; |
|
m_hLeftSmoke->m_MinSpeed = 2; |
|
m_hLeftSmoke->m_MaxSpeed = 24; |
|
|
|
m_hLeftSmoke->SetLifetime( 1e6 ); |
|
m_hLeftSmoke->FollowEntity( this, "left" ); |
|
} |
|
} |
|
} |
|
|
|
// hit hard, hits cockpit, hits engines |
|
if (flDamage > 50 || ptr->hitgroup == 1 || ptr->hitgroup == 2 || ptr->hitgroup == 3) |
|
{ |
|
AddMultiDamage( info, this ); |
|
} |
|
else |
|
{ |
|
g_pEffects->Sparks( ptr->endpos ); |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
void CNPC_Osprey::InitializeRotorSound( void ) |
|
{ |
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); |
|
|
|
CPASAttenuationFilter filter( this ); |
|
m_pRotorSound = controller.SoundCreate( filter, entindex(), CHAN_STATIC, "Apache.RotorSpinup", 0.2 ); |
|
|
|
BaseClass::InitializeRotorSound(); |
|
} |
|
|
|
void CNPC_Osprey::FindAllThink( void ) |
|
{ |
|
CBaseEntity *pEntity = NULL; |
|
|
|
m_iUnits = 0; |
|
while ( ( pEntity = gEntList.FindEntityByClassname( pEntity, "monster_human_grunt" ) ) != NULL) |
|
{ |
|
if ( m_iUnits > MAX_CARRY ) |
|
break; |
|
|
|
if (pEntity->IsAlive()) |
|
{ |
|
m_hGrunt[m_iUnits] = pEntity; |
|
m_vecOrigin[m_iUnits] = pEntity->GetAbsOrigin(); |
|
m_iUnits++; |
|
} |
|
} |
|
|
|
if (m_iUnits == 0) |
|
{ |
|
Msg( "osprey error: no grunts to resupply\n"); |
|
UTIL_Remove( this ); |
|
return; |
|
} |
|
|
|
m_startTime = 0.0f; |
|
} |
|
|
|
void CNPC_Osprey::DeployThink( void ) |
|
{ |
|
Vector vecForward; |
|
Vector vecRight; |
|
Vector vecUp; |
|
Vector vecSrc; |
|
|
|
SetLocalAngularVelocity( QAngle ( 0, 0, 0 ) ); |
|
SetAbsVelocity( Vector ( 0, 0, 0 ) ); |
|
|
|
AngleVectors( GetAbsAngles(), &vecForward, &vecRight, &vecUp ); |
|
|
|
trace_t tr; |
|
UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, -4096.0), MASK_SOLID_BRUSHONLY, this,COLLISION_GROUP_NONE, &tr); |
|
CSoundEnt::InsertSound ( SOUND_DANGER, tr.endpos, 400, 0.3 ); |
|
|
|
vecSrc = GetAbsOrigin() + vecForward * 32 + vecRight * 100 + vecUp * -96; |
|
m_hRepel[0] = MakeGrunt( vecSrc ); |
|
|
|
vecSrc = GetAbsOrigin() + vecForward * -64 + vecRight * 100 + vecUp * -96; |
|
m_hRepel[1] = MakeGrunt( vecSrc ); |
|
|
|
vecSrc = GetAbsOrigin() + vecForward * 32 + vecRight * -100 + vecUp * -96; |
|
m_hRepel[2] = MakeGrunt( vecSrc ); |
|
|
|
vecSrc = GetAbsOrigin() + vecForward * -64 + vecRight * -100 + vecUp * -96; |
|
m_hRepel[3] = MakeGrunt( vecSrc ); |
|
|
|
m_iRepelState = GRUNTS_DEPLOYED; |
|
|
|
HoverThink(); |
|
} |
|
|
|
bool CNPC_Osprey::HasDead( ) |
|
{ |
|
for (int i = 0; i < m_iUnits; i++) |
|
{ |
|
if (m_hGrunt[i] == NULL || !m_hGrunt[i]->IsAlive()) |
|
{ |
|
return TRUE; |
|
} |
|
else |
|
{ |
|
m_vecOrigin[i] = m_hGrunt[i]->GetAbsOrigin(); // send them to where they died |
|
} |
|
} |
|
return FALSE; |
|
} |
|
|
|
void CNPC_Osprey::HoverThink( void ) |
|
{ |
|
int i = 0; |
|
for (i = 0; i < 4; i++) |
|
{ |
|
CBaseEntity *pRepel = (CBaseEntity*)m_hRepel[i]; |
|
|
|
if ( pRepel != NULL && pRepel->m_iHealth > 0 && pRepel->GetMoveType() == MOVETYPE_FLYGRAVITY ) |
|
{ |
|
break; |
|
} |
|
} |
|
|
|
if ( i == 4 ) |
|
m_iRepelState = LOADED_WITH_GRUNTS; |
|
|
|
if( m_iRepelState != LOADED_WITH_GRUNTS ) |
|
{ |
|
// angle of engines should approach vertical |
|
m_iRotorAngle = UTIL_Approach(0, m_iRotorAngle, 5); |
|
} |
|
} |
|
|
|
CAI_BaseNPC *CNPC_Osprey::MakeGrunt( Vector vecSrc ) |
|
{ |
|
CBaseEntity *pEntity; |
|
CAI_BaseNPC *pGrunt; |
|
|
|
trace_t tr; |
|
UTIL_TraceLine( vecSrc, vecSrc + Vector( 0, 0, -4096.0), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr); |
|
|
|
if ( tr.m_pEnt && tr.m_pEnt->GetSolid() != SOLID_BSP) |
|
return NULL; |
|
|
|
for (int i = 0; i < m_iUnits; i++) |
|
{ |
|
if (m_hGrunt[i] == NULL || !m_hGrunt[i]->IsAlive()) |
|
{ |
|
if (m_hGrunt[i] != NULL && m_hGrunt[i]->m_nRenderMode == kRenderNormal) |
|
{ |
|
m_hGrunt[i]->SUB_StartFadeOut( ); |
|
} |
|
pEntity = Create( "monster_human_grunt", vecSrc, GetAbsAngles() ); |
|
pGrunt = pEntity->MyNPCPointer( ); |
|
pGrunt->SetMoveType( MOVETYPE_FLYGRAVITY ); |
|
pGrunt->SetGravity( 0.0001 ); |
|
|
|
Vector spd = Vector( 0, 0, random->RandomFloat( -196, -128 ) ); |
|
pGrunt->SetLocalVelocity( spd ); |
|
pGrunt->SetActivity( ACT_GLIDE ); |
|
pGrunt->SetGroundEntity( NULL ); |
|
|
|
pGrunt->SetOwnerEntity(this); |
|
|
|
|
|
CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.vmt", 1.0 ); |
|
pBeam->PointEntInit( vecSrc + Vector(0,0,112), pGrunt ); |
|
pBeam->SetBeamFlags( FBEAM_SOLID ); |
|
pBeam->SetColor( 255, 255, 255 ); |
|
pBeam->SetThink( &CBaseEntity::SUB_Remove ); |
|
pBeam->SetNextThink( gpGlobals->curtime + -4096.0 * tr.fraction / pGrunt->GetAbsVelocity().z + 0.5 ); |
|
|
|
|
|
// ALERT( at_console, "%d at %.0f %.0f %.0f\n", i, m_vecOrigin[i].x, m_vecOrigin[i].y, m_vecOrigin[i].z ); |
|
pGrunt->m_vecLastPosition = m_vecOrigin[i]; |
|
m_hGrunt[i] = pGrunt; |
|
return pGrunt; |
|
} |
|
} |
|
// ALERT( at_console, "none dead\n"); |
|
return NULL; |
|
} |
|
|
|
void CNPC_Osprey::Flight( void ) |
|
{ |
|
if ( m_iRepelState == LOADED_WITH_GRUNTS ) |
|
{ |
|
BaseClass::Flight(); |
|
|
|
// adjust angle of osprey rotors |
|
if ( m_angleVelocity > 0 ) |
|
{ |
|
m_iRotorAngle = UTIL_Approach(-45, m_iRotorAngle, 5 * (m_angleVelocity / 10)); |
|
} |
|
else |
|
{ |
|
m_iRotorAngle = UTIL_Approach(-1, m_iRotorAngle, 5); |
|
} |
|
SetBoneController( 0, m_iRotorAngle ); |
|
|
|
} |
|
} |
|
|
|
void CNPC_Osprey::PrescheduleThink( void ) |
|
{ |
|
BaseClass::PrescheduleThink(); |
|
|
|
StudioFrameAdvance( ); |
|
|
|
if ( m_startTime != 0.0 && m_startTime <= gpGlobals->curtime ) |
|
FindAllThink(); |
|
|
|
if ( GetGoalEnt() ) |
|
{ |
|
if ( m_flPrevGoalVel != GetGoalEnt()->m_flSpeed ) |
|
{ |
|
if ( m_flPrevGoalVel == 0 && GetGoalEnt()->m_flSpeed != 0 ) |
|
{ |
|
if ( HasDead() && m_iRepelState == LOADED_WITH_GRUNTS ) |
|
m_iRepelState = UNLOADING_GRUNTS; |
|
} |
|
|
|
m_flPrevGoalVel = GetGoalEnt()->m_flSpeed; |
|
} |
|
} |
|
|
|
if ( m_iRepelState == UNLOADING_GRUNTS ) |
|
DeployThink(); |
|
else if ( m_iRepelState == GRUNTS_DEPLOYED ) |
|
HoverThink(); |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Lame, temporary death |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Osprey::DyingThink( void ) |
|
{ |
|
StudioFrameAdvance( ); |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
|
|
if( gpGlobals->curtime > m_flNextCrashExplosion ) |
|
{ |
|
CPASFilter filter( GetAbsOrigin() ); |
|
Vector pos; |
|
QAngle dummy; |
|
|
|
int rand = RandomInt(0,10); |
|
|
|
if( rand < 4 ) |
|
{ |
|
int iAttach = LookupAttachment( rand % 2 ? "left" : "right" ); |
|
GetAttachment( iAttach, pos, dummy ); |
|
} |
|
else |
|
{ |
|
pos = GetAbsOrigin(); |
|
pos.x += random->RandomFloat( -150, 150 ); |
|
pos.y += random->RandomFloat( -150, 150 ); |
|
pos.z += random->RandomFloat( -150, -50 ); |
|
} |
|
|
|
te->Explosion( filter, 0.0, &pos, g_sModelIndexFireball, 10, 15, TE_EXPLFLAG_NONE, 100, 0 ); |
|
m_flNextCrashExplosion = gpGlobals->curtime + random->RandomFloat( 0.4, 0.7 ); |
|
|
|
Vector vecSize = Vector( 500, 500, 60 ); |
|
|
|
switch( m_iNextCrashModel ) |
|
{ |
|
case 0: |
|
{ |
|
te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle, |
|
vecSize, vec3_origin, m_iTailGibs, 100, 0, 2.5, BREAK_METAL ); |
|
break; |
|
} |
|
case 1: |
|
{ |
|
te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle, |
|
vecSize, vec3_origin, m_iBodyGibs, 100, 0, 2.5, BREAK_METAL ); |
|
break; |
|
} |
|
case 2: |
|
{ |
|
te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle, |
|
vecSize, vec3_origin, m_iEngineGibs, 100, 0, 2.5, BREAK_METAL ); |
|
break; |
|
} |
|
case 3: |
|
{ |
|
te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle, |
|
vecSize, vec3_origin, m_nDebrisModel, 100, 0, 2.5, BREAK_METAL ); |
|
break; |
|
} |
|
} |
|
|
|
m_iNextCrashModel++; |
|
if( m_iNextCrashModel > 3 ) m_iNextCrashModel = 0; |
|
} |
|
|
|
QAngle angVel = GetLocalAngularVelocity(); |
|
if( angVel.y < 400 ) |
|
{ |
|
angVel.y *= 1.1; |
|
SetLocalAngularVelocity( angVel ); |
|
} |
|
Vector vecImpulse( 0, 0, 0 ); |
|
// add gravity |
|
vecImpulse.z -= 38.4; // 32ft/sec |
|
ApplyAbsVelocityImpulse( vecImpulse ); |
|
|
|
} |
|
|
|
void CNPC_Osprey::CrashTouch( CBaseEntity *pOther ) |
|
{ |
|
Vector vecSize = Vector( 120, 120, 30 ); |
|
CPVSFilter filter( GetAbsOrigin() ); |
|
|
|
te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle, |
|
vecSize, vec3_origin, m_iTailGibs, 100, 0, 2.5, BREAK_METAL ); |
|
|
|
if( m_hLeftSmoke ) |
|
{ |
|
m_hLeftSmoke->SetLifetime(0.1f); |
|
m_hLeftSmoke = NULL; |
|
} |
|
|
|
if( m_hRightSmoke ) |
|
{ |
|
m_hRightSmoke->SetLifetime(0.1f); |
|
m_hRightSmoke = NULL; |
|
} |
|
|
|
BaseClass::CrashTouch( pOther ); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
BEGIN_DATADESC( CBaseHelicopter ) |
|
|
|
DEFINE_THINKFUNC( HelicopterThink ), |
|
DEFINE_THINKFUNC( CallDyingThink ), |
|
DEFINE_ENTITYFUNC( CrashTouch ), |
|
DEFINE_ENTITYFUNC( FlyTouch ), |
|
|
|
DEFINE_SOUNDPATCH( m_pRotorSound ), |
|
|
|
DEFINE_FIELD( m_flForce, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_fHelicopterFlags, FIELD_INTEGER), |
|
DEFINE_FIELD( m_vecDesiredFaceDir, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_vecDesiredPosition,FIELD_POSITION_VECTOR ), |
|
DEFINE_FIELD( m_vecGoalOrientation,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_vecTarget, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_vecTargetPosition, FIELD_POSITION_VECTOR ), |
|
|
|
DEFINE_FIELD( m_angleVelocity, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flNextCrashExplosion, FIELD_TIME ), |
|
|
|
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" ), |
|
|
|
// Inputs |
|
DEFINE_INPUTFUNC( FIELD_STRING, "ChangePathCorner", InputChangePathCorner), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate), |
|
|
|
// Outputs |
|
DEFINE_OUTPUT(m_AtTarget, "AtPathCorner" ), |
|
DEFINE_OUTPUT(m_LeaveTarget, "LeavePathCorner" ),//<<TEMP>> Undone |
|
|
|
END_DATADESC() |
|
|
|
IMPLEMENT_SERVERCLASS_ST( CBaseHelicopter, DT_BaseHelicopter ) |
|
END_SEND_TABLE() |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// 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 ); |
|
|
|
m_lifeState = LIFE_ALIVE; |
|
|
|
// 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; |
|
|
|
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 ); |
|
} |
|
|
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
bool CBaseHelicopter::FireGun( void ) |
|
{ |
|
return true; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : The main think function for the helicopters |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
void CBaseHelicopter::HelicopterThink( void ) |
|
{ |
|
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. |
|
DispatchAnimEvents( this ); |
|
|
|
PrescheduleThink(); |
|
|
|
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(); |
|
|
|
HelicopterPostThink(); |
|
} |
|
|
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
void CBaseHelicopter::Hunt( void ) |
|
{ |
|
FlyPathCorners( ); |
|
|
|
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( 4092 ); |
|
|
|
ChooseEnemy(); |
|
|
|
if( HasEnemy() ) |
|
{ |
|
CheckEnemy( GetEnemy() ); |
|
|
|
if (FVisible( GetEnemy() )) |
|
{ |
|
if (m_flLastSeen < gpGlobals->curtime - 2) |
|
{ |
|
m_flPrevSeen = gpGlobals->curtime; |
|
} |
|
|
|
m_flLastSeen = gpGlobals->curtime; |
|
m_vecTargetPosition = GetEnemy()->WorldSpaceCenter(); |
|
} |
|
} |
|
else |
|
{ |
|
// look at where we're going instead |
|
m_vecTargetPosition = m_vecDesiredPosition; |
|
} |
|
} |
|
else |
|
{ |
|
// If we're dead or dying, forget our enemy and don't look for new ones(sjb) |
|
SetEnemy( NULL ); |
|
} |
|
|
|
if ( 1 ) |
|
{ |
|
Vector targetDir = m_vecTargetPosition - GetAbsOrigin(); |
|
Vector desiredDir = m_vecDesiredPosition - 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 = m_vecGoalOrientation; |
|
} |
|
|
|
Flight(); |
|
|
|
UpdatePlayerDopplerShift( ); |
|
|
|
// 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 > m_flMaxSpeedFiring ) |
|
{ |
|
m_flGoalSpeed = m_flMaxSpeedFiring; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (m_fHelicopterFlags & BITS_HELICOPTER_MISSILE_ON) |
|
{ |
|
AimRocketGun(); |
|
} |
|
|
|
// Finally, forget dead enemies. |
|
if( GetEnemy() != NULL && !GetEnemy()->IsAlive() ) |
|
{ |
|
SetEnemy( NULL ); |
|
} |
|
} |
|
|
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
void CBaseHelicopter::FlyPathCorners( void ) |
|
{ |
|
|
|
if ( GetGoalEnt() == NULL && m_target != NULL_STRING )// this monster has a target |
|
{ |
|
SetGoalEnt( gEntList.FindEntityByName( NULL, m_target ) ); |
|
if (GetGoalEnt()) |
|
{ |
|
m_vecDesiredPosition = GetGoalEnt()->GetLocalOrigin(); |
|
|
|
// FIXME: orienation removed from path_corners! |
|
AngleVectors( GetGoalEnt()->GetLocalAngles(), &m_vecGoalOrientation ); |
|
} |
|
} |
|
|
|
// walk route |
|
if (GetGoalEnt()) |
|
{ |
|
// ALERT( at_console, "%.0f\n", flLength ); |
|
if ( HasReachedTarget( ) ) |
|
{ |
|
// If we get this close to the desired position, it's assumed that we've reached |
|
// the desired position, so move on. |
|
|
|
// Fire target that I've reached my goal |
|
m_AtTarget.FireOutput( GetGoalEnt(), this ); |
|
|
|
OnReachedTarget( GetGoalEnt() ); |
|
|
|
SetGoalEnt( gEntList.FindEntityByName( NULL, GetGoalEnt()->m_target ) ); |
|
|
|
if (GetGoalEnt()) |
|
{ |
|
m_vecDesiredPosition = GetGoalEnt()->GetAbsOrigin(); |
|
|
|
// FIXME: orienation removed from path_corners! |
|
AngleVectors( GetGoalEnt()->GetLocalAngles(), &m_vecGoalOrientation ); |
|
|
|
// NDebugOverlay::Box( m_vecDesiredPosition, Vector( -16, -16, -16 ), Vector( 16, 16, 16 ), 0,255,0, false, 30.0); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// If we can't find a new target, just stay where we are. |
|
m_vecDesiredPosition = GetAbsOrigin(); |
|
} |
|
|
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// 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 = pPlayer->GetLocalOrigin() - GetLocalOrigin(); |
|
VectorNormalize(dir); |
|
|
|
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)); |
|
|
|
// clamp pitch shifts |
|
if (iPitch > 250) |
|
iPitch = 250; |
|
if (iPitch < 50) |
|
iPitch = 50; |
|
|
|
// Msg( "Pitch:%d\n", iPitch ); |
|
|
|
UpdateRotorSoundPitch( iPitch ); |
|
//Msg( "%.0f\n", pitch ); |
|
//Msg( "%.0f\n", flVol ); |
|
} |
|
else |
|
{ |
|
Msg( "Chopper didn't find a player!\n" ); |
|
} |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
//------------------------------------------------------------------------------ |
|
void CBaseHelicopter::Flight( void ) |
|
{ |
|
if( GetFlags() & FL_ONGROUND ) |
|
{ |
|
//This would be really bad. |
|
SetGroundEntity( NULL ); |
|
} |
|
|
|
// Generic speed up |
|
if (m_flGoalSpeed < m_flMaxSpeed) |
|
{ |
|
m_flGoalSpeed += GetAcceleration(); |
|
} |
|
|
|
// NDebugOverlay::Line(GetAbsOrigin(), m_vecDesiredPosition, 0,0,255, true, 0.1); |
|
|
|
// estimate where I'll be facing in one seconds |
|
Vector forward, right, up; |
|
AngleVectors( GetLocalAngles() + GetLocalAngularVelocity() * 2, &forward, &right, &up ); |
|
|
|
QAngle angVel = GetLocalAngularVelocity(); |
|
float flSide = DotProduct( m_vecDesiredFaceDir, right ); |
|
if (flSide < 0) |
|
{ |
|
if ( angVel.y < 8 ) |
|
{ |
|
angVel.y += 2; |
|
} |
|
else if (angVel.y < 60) |
|
{ |
|
angVel.y += 8; |
|
} |
|
} |
|
else |
|
{ |
|
if ( angVel.y > -8 ) |
|
{ |
|
angVel.y -= 2; |
|
} |
|
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, NULL, NULL, &up ); |
|
Vector vecEst = GetAbsOrigin() + GetAbsVelocity() * 2.0 + up * m_flForce * 20 - Vector( 0, 0, 384 * 2 ); |
|
|
|
// add immediate force |
|
AngleVectors( GetLocalAngles(), &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( m_vecDesiredPosition - vecEst, forward ); |
|
|
|
// float flDist = (m_vecDesiredPosition - vecEst).Length(); |
|
|
|
// float flSlip = DotProduct( GetAbsVelocity(), right ); |
|
float flSlip = -DotProduct( m_vecDesiredPosition - 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 < m_vecDesiredPosition.z) |
|
{ |
|
m_flForce += FORCE_POSDELTA; |
|
} |
|
else if (m_flForce > 30) |
|
{ |
|
if (vecEst.z > m_vecDesiredPosition.z) |
|
m_flForce -= FORCE_NEGDELTA; |
|
} |
|
|
|
// pitch forward or back to get to target |
|
//----------------------------------------- |
|
// Pitch is reversed since Half-Life! (sjb) |
|
//----------------------------------------- |
|
|
|
|
|
// when we're way out, lean forward up to 40 degrees to accelerate to target |
|
// not exceeding our goal speed. |
|
#if 0 |
|
float nodeSpeed = GetGoalEnt()->m_flSpeed; |
|
|
|
if ( flDist > 300 && flSpeed < m_flGoalSpeed && GetLocalAngles().x + angVel.x < 25 ) |
|
{ |
|
angVel.x += 8.0; //lean forward |
|
} |
|
else if ( flDist < 500 && GetLocalAngles().x + angVel.x > -30 && nodeSpeed == 0) |
|
{ |
|
angVel.x -= 8.0; // lean backwards as we approach the target |
|
} |
|
else if (GetLocalAngles().x + angVel.x < -20 ) |
|
{ |
|
// ALERT( at_console, "f " ); |
|
angVel.x += 2.0; |
|
} |
|
else if (GetLocalAngles().x + angVel.x > 20 ) |
|
{ |
|
// ALERT( at_console, "b " ); |
|
angVel.x -= 2.0; |
|
} |
|
#else |
|
m_angleVelocity = GetLocalAngles().x + angVel.x; |
|
float angleX = angVel.x; |
|
|
|
// Msg("AngVel %f, %f\n", m_angleVelocity, flDist); |
|
if (flDist > 128 && flSpeed < m_flGoalSpeed && m_angleVelocity < 30) |
|
{ |
|
// ALERT( at_console, "F " ); |
|
// lean forward |
|
angleX += 6; |
|
} |
|
else if (flDist < -128 && flSpeed > -50 && m_angleVelocity > -20) |
|
{ |
|
// ALERT( at_console, "B " ); |
|
// lean backward |
|
angleX -= 12.0; |
|
} |
|
else if ( (m_angleVelocity < -20) || (m_angleVelocity < 0 && flDist < 128) ) |
|
{ |
|
// ALERT( at_console, "f " ); |
|
if ( abs(m_angleVelocity) < 5 ) |
|
{ |
|
angleX += 1.0; |
|
} |
|
else |
|
{ |
|
angleX += 4.0; |
|
} |
|
} |
|
else if ( (m_angleVelocity > 20) || (m_angleVelocity > 0 && flDist < 128) ) |
|
{ |
|
// ALERT( at_console, "b " ); |
|
if ( abs(m_angleVelocity) < 5 ) |
|
{ |
|
angleX -= 1.0; |
|
} |
|
else |
|
{ |
|
angleX -= 4.0; |
|
} |
|
} |
|
|
|
angVel.x = angleX; |
|
//Msg("AngVel.x %f %f\n", angVel.x, angleX ); |
|
|
|
#endif |
|
|
|
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 ); |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
void CBaseHelicopter::InitializeRotorSound( void ) |
|
{ |
|
if (m_pRotorSound) |
|
{ |
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); |
|
|
|
// Get the rotor sound started up. |
|
controller.Play( m_pRotorSound, 0.0, 100 ); |
|
controller.SoundChangeVolume(m_pRotorSound, GetRotorVolume(), 2.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 ); |
|
controller.SoundChangeVolume( m_pRotorSound, GetRotorVolume(), 0.1 ); |
|
} |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
void CBaseHelicopter::FlyTouch( CBaseEntity *pOther ) |
|
{ |
|
// bounce if we hit something solid |
|
if ( pOther->GetSolid() == SOLID_BSP) |
|
{ |
|
const trace_t &tr = CBaseEntity::GetTouchTrace( ); |
|
|
|
// UNDONE, do a real bounce |
|
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 ); |
|
|
|
CPASFilter filter( GetAbsOrigin() ); |
|
for (int i = 0; i < 5; i++) |
|
{ |
|
Vector pos = GetAbsOrigin(); |
|
|
|
pos.x += random->RandomFloat( -150, 150 ); |
|
pos.y += random->RandomFloat( -150, 150 ); |
|
pos.z += random->RandomFloat( -150, -50 ); |
|
te->Explosion( filter, MIN( 0.99, i * 0.2 ), &pos, g_sModelIndexFireball, 10, 15, TE_EXPLFLAG_NONE, 100, 0 ); |
|
} |
|
|
|
UTIL_Remove( this ); |
|
} |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
void CBaseHelicopter::DyingThink( void ) |
|
{ |
|
StudioFrameAdvance( ); |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
|
|
SetLocalAngularVelocity( GetLocalAngularVelocity() * 1.02 ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
int CBaseHelicopter::OnTakeDamage_Alive( const CTakeDamageInfo &info ) |
|
{ |
|
#if 0 |
|
// This code didn't port easily. WTF does it do? (sjb) |
|
if (pevInflictor->m_owner == pev) |
|
return 0; |
|
#endif |
|
|
|
/* |
|
if ( (bitsDamageType & DMG_BULLET) && flDamage > 50) |
|
{ |
|
// clip bullet damage at 50 |
|
flDamage = 50; |
|
} |
|
*/ |
|
|
|
return BaseClass::OnTakeDamage_Alive( info ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// 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(), m_vecDesiredPosition, 0,0,255, true, 0); |
|
} |
|
} |
|
BaseClass::DrawDebugGeometryOverlays(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void CBaseHelicopter::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) |
|
{ |
|
CTakeDamageInfo dmgInfo = info; |
|
|
|
// ALERT( at_console, "%d %.0f\n", ptr->iHitgroup, flDamage ); |
|
|
|
// HITGROUPS don't work currently. |
|
// ignore blades |
|
// if (ptr->hitgroup == 6 && (info.GetDamageType() & (DMG_ENERGYBEAM|DMG_BULLET|DMG_CLUB))) |
|
// return; |
|
|
|
// hit hard, hits cockpit |
|
if (info.GetDamage() > 50 || ptr->hitgroup == 11 ) |
|
{ |
|
// ALERT( at_console, "%map .0f\n", flDamage ); |
|
AddMultiDamage( dmgInfo, this ); |
|
// m_iDoSmokePuff = 3 + (info.GetDamage() / 5.0); |
|
} |
|
else |
|
{ |
|
// do half damage in the body |
|
dmgInfo.ScaleDamage(0.5); |
|
AddMultiDamage( dmgInfo, this ); |
|
g_pEffects->Ricochet( ptr->endpos, ptr->plane.normal ); |
|
} |
|
|
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
void CBaseHelicopter::NullThink( void ) |
|
{ |
|
StudioFrameAdvance( ); |
|
SetNextThink( gpGlobals->curtime + 0.5f ); |
|
} |
|
|
|
|
|
void CBaseHelicopter::Startup( void ) |
|
{ |
|
m_flGoalSpeed = m_flInitialSpeed; |
|
SetThink( &CBaseHelicopter::HelicopterThink ); |
|
SetTouch( &CBaseHelicopter::FlyTouch ); |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
} |
|
|
|
|
|
void CBaseHelicopter::Event_Killed( const CTakeDamageInfo &info ) |
|
{ |
|
m_lifeState = LIFE_DYING; |
|
|
|
SetMoveType( MOVETYPE_FLYGRAVITY ); |
|
SetGravity( UTIL_ScaleForGravity( 240 ) ); // use a lower gravity |
|
|
|
// Kill the rotor sound. |
|
|
|
UTIL_SetSize( this, Vector( -32, -32, -64), Vector( 32, 32, 0) ); |
|
SetThink( &CBaseHelicopter::CallDyingThink ); |
|
SetTouch( &CBaseHelicopter::CrashTouch ); |
|
|
|
m_flNextCrashExplosion = gpGlobals->curtime + 0.0f; |
|
|
|
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; |
|
} |
|
*/ |
|
m_OnDeath.FireOutput( info.GetAttacker(), this ); |
|
} |
|
|
|
|
|
void CBaseHelicopter::StopLoopingSounds() |
|
{ |
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); |
|
controller.SoundDestroy( m_pRotorSound ); |
|
m_pRotorSound = NULL; |
|
|
|
BaseClass::StopLoopingSounds(); |
|
} |
|
|
|
void CBaseHelicopter::GibMonster( void ) |
|
{ |
|
} |
|
|
|
|
|
void CBaseHelicopter::ChangePathCorner( const char *pszName ) |
|
{ |
|
if( m_lifeState != LIFE_ALIVE ) |
|
{ |
|
// Disregard this if dead or dying. |
|
return; |
|
} |
|
|
|
if (GetGoalEnt()) |
|
{ |
|
SetGoalEnt( gEntList.FindEntityByName( NULL, pszName ) ); |
|
|
|
// I don't think we need to do this. The FLIGHT() code will do it for us (sjb) |
|
if (GetGoalEnt()) |
|
{ |
|
m_vecDesiredPosition = GetGoalEnt()->GetLocalOrigin(); |
|
AngleVectors( GetGoalEnt()->GetLocalAngles(), &m_vecGoalOrientation ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Slams the chopper's current path corner and sends it to a new one. |
|
// This code does NOT check that the path from the current position |
|
// to the wished path corner is clear! |
|
//----------------------------------------------------------------------------- |
|
void CBaseHelicopter::InputChangePathCorner( inputdata_t &inputdata ) |
|
{ |
|
ChangePathCorner( inputdata.value.String() ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// 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: |
|
// 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() ) && pNewEnemy != NULL ) |
|
{ |
|
//New enemy! Clear the timers and set conditions. |
|
SetEnemy( pNewEnemy ); |
|
m_flLastSeen = m_flPrevSeen = gpGlobals->curtime; |
|
return true; |
|
} |
|
else |
|
{ |
|
ClearCondition( COND_NEW_ENEMY ); |
|
return false; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void CBaseHelicopter::CheckEnemy( CBaseEntity *pEnemy ) |
|
{ |
|
// ------------------- |
|
// If enemy is dead |
|
// ------------------- |
|
if ( !pEnemy->IsAlive() ) |
|
{ |
|
SetCondition( COND_ENEMY_DEAD ); |
|
ClearCondition( COND_SEE_ENEMY ); |
|
ClearCondition( COND_ENEMY_OCCLUDED ); |
|
return; |
|
} |
|
} |
|
|
|
bool CBaseHelicopter::HasReachedTarget( void ) |
|
{ |
|
float flDist = (WorldSpaceCenter() - m_vecDesiredPosition).Length(); |
|
|
|
if( GetGoalEnt()->m_flSpeed <= 0 ) |
|
return ( flDist < 145 ); |
|
else |
|
return( flDist < 512 ); |
|
} |
|
|
|
|