Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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.
 
 
 
 
 
 

1105 lines
33 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "vehicle_apc.h"
#include "ammodef.h"
#include "IEffects.h"
#include "engine/IEngineSound.h"
#include "weapon_rpg.h"
#include "in_buttons.h"
#include "globalstate.h"
#include "soundent.h"
#include "ai_basenpc.h"
#include "ndebugoverlay.h"
#include "gib.h"
#include "EntityFlame.h"
#include "smoke_trail.h"
#include "explode.h"
#include "effect_dispatch_data.h"
#include "te_effect_dispatch.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define ROCKET_ATTACK_RANGE_MAX 5500.0f
#define ROCKET_ATTACK_RANGE_MIN 1250.0f
#define MACHINE_GUN_ATTACK_RANGE_MAX 1250.0f
#define MACHINE_GUN_ATTACK_RANGE_MIN 0.0f
#define MACHINE_GUN_MAX_UP_PITCH 30
#define MACHINE_GUN_MAX_DOWN_PITCH 10
#define MACHINE_GUN_MAX_LEFT_YAW 30
#define MACHINE_GUN_MAX_RIGHT_YAW 30
#define MACHINE_GUN_BURST_SIZE 10
#define MACHINE_GUN_BURST_TIME 0.075f
#define MACHINE_GUN_BURST_PAUSE_TIME 2.0f
#define ROCKET_SALVO_SIZE 5
#define ROCKET_DELAY_TIME 1.5
#define ROCKET_MIN_BURST_PAUSE_TIME 3
#define ROCKET_MAX_BURST_PAUSE_TIME 4
#define ROCKET_SPEED 800
#define DEATH_VOLLEY_ROCKET_COUNT 4
#define DEATH_VOLLEY_MIN_FIRE_TIME 0.333
#define DEATH_VOLLEY_MAX_FIRE_TIME 0.166
extern short g_sModelIndexFireball; // Echh...
ConVar sk_apc_health( "sk_apc_health", "750" );
#define APC_MAX_CHUNKS 3
static const char *s_pChunkModelName[APC_MAX_CHUNKS] =
{
"models/gibs/helicopter_brokenpiece_01.mdl",
"models/gibs/helicopter_brokenpiece_02.mdl",
"models/gibs/helicopter_brokenpiece_03.mdl",
};
#define APC_MAX_GIBS 6
static const char *s_pGibModelName[APC_MAX_GIBS] =
{
"models/combine_apc_destroyed_gib01.mdl",
"models/combine_apc_destroyed_gib02.mdl",
"models/combine_apc_destroyed_gib03.mdl",
"models/combine_apc_destroyed_gib04.mdl",
"models/combine_apc_destroyed_gib05.mdl",
"models/combine_apc_destroyed_gib06.mdl",
};
LINK_ENTITY_TO_CLASS( prop_vehicle_apc, CPropAPC );
BEGIN_DATADESC( CPropAPC )
DEFINE_FIELD( m_flDangerSoundTime, FIELD_TIME ),
DEFINE_FIELD( m_flHandbrakeTime, FIELD_TIME ),
DEFINE_FIELD( m_bInitialHandbrake, FIELD_BOOLEAN ),
DEFINE_FIELD( m_nSmokeTrailCount, FIELD_INTEGER ),
DEFINE_FIELD( m_flMachineGunTime, FIELD_TIME ),
DEFINE_FIELD( m_iMachineGunBurstLeft, FIELD_INTEGER ),
// DEFINE_FIELD( m_nMachineGunMuzzleAttachment, FIELD_INTEGER ),
// DEFINE_FIELD( m_nMachineGunBaseAttachment, FIELD_INTEGER ),
// DEFINE_FIELD( m_vecBarrelPos, FIELD_VECTOR ),
DEFINE_FIELD( m_bInFiringCone, FIELD_BOOLEAN ),
// DEFINE_FIELD( m_hLaserDot, FIELD_EHANDLE ),
DEFINE_FIELD( m_hRocketTarget, FIELD_EHANDLE ),
DEFINE_FIELD( m_iRocketSalvoLeft, FIELD_INTEGER ),
DEFINE_FIELD( m_flRocketTime, FIELD_TIME ),
// DEFINE_FIELD( m_nRocketAttachment, FIELD_INTEGER ),
DEFINE_FIELD( m_nRocketSide, FIELD_INTEGER ),
DEFINE_FIELD( m_hSpecificRocketTarget, FIELD_EHANDLE ),
DEFINE_KEYFIELD( m_strMissileHint, FIELD_STRING, "missilehint" ),
DEFINE_INPUTFUNC( FIELD_VOID, "Destroy", InputDestroy ),
DEFINE_INPUTFUNC( FIELD_STRING, "FireMissileAt", InputFireMissileAt ),
DEFINE_OUTPUT( m_OnDeath, "OnDeath" ),
DEFINE_OUTPUT( m_OnFiredMissile, "OnFiredMissile" ),
DEFINE_OUTPUT( m_OnDamaged, "OnDamaged" ),
DEFINE_OUTPUT( m_OnDamagedByPlayer, "OnDamagedByPlayer" ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropAPC::Precache( void )
{
BaseClass::Precache();
int i;
for ( i = 0; i < APC_MAX_CHUNKS; ++i )
{
PrecacheModel( s_pChunkModelName[i] );
}
for ( i = 0; i < APC_MAX_GIBS; ++i )
{
PrecacheModel( s_pGibModelName[i] );
}
PrecacheScriptSound( "Weapon_AR2.Single" );
PrecacheScriptSound( "PropAPC.FireRocket" );
PrecacheScriptSound( "combine.door_lock" );
}
//------------------------------------------------
// Spawn
//------------------------------------------------
void CPropAPC::Spawn( void )
{
BaseClass::Spawn();
SetBlocksLOS( true );
m_iHealth = m_iMaxHealth = sk_apc_health.GetFloat();
SetCycle( 0 );
m_iMachineGunBurstLeft = MACHINE_GUN_BURST_SIZE;
m_iRocketSalvoLeft = ROCKET_SALVO_SIZE;
m_nRocketSide = 0;
m_lifeState = LIFE_ALIVE;
m_bInFiringCone = false;
m_flHandbrakeTime = gpGlobals->curtime + 0.1;
m_bInitialHandbrake = false;
// Reset the gun to a default pose.
SetPoseParameter( "vehicle_weapon_pitch", 0 );
SetPoseParameter( "vehicle_weapon_yaw", 90 );
CreateAPCLaserDot();
if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE )
{
AddFlag( FL_AIMTARGET );
}
}
//-----------------------------------------------------------------------------
// Purpose: Create a laser
//-----------------------------------------------------------------------------
void CPropAPC::CreateAPCLaserDot( void )
{
// Create a laser if we don't have one
if ( m_hLaserDot == NULL )
{
m_hLaserDot = CreateLaserDot( GetAbsOrigin(), this, false );
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CPropAPC::ShouldAttractAutoAim( CBaseEntity *pAimingEnt )
{
if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE && pAimingEnt->IsPlayer() && GetDriver() )
{
return true;
}
return BaseClass::ShouldAttractAutoAim( pAimingEnt );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropAPC::Activate()
{
BaseClass::Activate();
m_nRocketAttachment = LookupAttachment( "cannon_muzzle" );
m_nMachineGunMuzzleAttachment = LookupAttachment( "muzzle" );
m_nMachineGunBaseAttachment = LookupAttachment( "gun_base" );
// NOTE: gun_ref must have the same position as gun_base, but rotates with the gun
int nMachineGunRefAttachment = LookupAttachment( "gun_def" );
Vector vecWorldBarrelPos;
matrix3x4_t matRefToWorld;
GetAttachment( m_nMachineGunMuzzleAttachment, vecWorldBarrelPos );
GetAttachment( nMachineGunRefAttachment, matRefToWorld );
VectorITransform( vecWorldBarrelPos, matRefToWorld, m_vecBarrelPos );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropAPC::UpdateOnRemove( void )
{
if ( m_hLaserDot )
{
UTIL_Remove( m_hLaserDot );
m_hLaserDot = NULL;
}
BaseClass::UpdateOnRemove();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropAPC::CreateServerVehicle( void )
{
// Create our armed server vehicle
m_pServerVehicle = new CAPCFourWheelServerVehicle();
m_pServerVehicle->SetVehicle( this );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pMoveData -
//-----------------------------------------------------------------------------
Class_T CPropAPC::ClassifyPassenger( CBaseCombatCharacter *pPassenger, Class_T defaultClassification )
{
return CLASS_COMBINE;
}
//-----------------------------------------------------------------------------
// Purpose: Damage events as modified for the passenger of the APC, not the APC itself
//-----------------------------------------------------------------------------
float CPropAPC::PassengerDamageModifier( const CTakeDamageInfo &info )
{
CTakeDamageInfo DmgInfo = info;
// bullets, slashing and headbutts don't hurt us in the apc, neither do rockets
if( (DmgInfo.GetDamageType() & DMG_BULLET) || (DmgInfo.GetDamageType() & DMG_SLASH) ||
(DmgInfo.GetDamageType() & DMG_CLUB) || (DmgInfo.GetDamageType() & DMG_BLAST) )
return (0);
// Accept everything else by default
return 1.0;
}
//-----------------------------------------------------------------------------
// position of eyes
//-----------------------------------------------------------------------------
Vector CPropAPC::EyePosition( )
{
Vector vecEyePosition;
CollisionProp()->NormalizedToWorldSpace( Vector( 0.5, 0.5, 1.0 ), &vecEyePosition );
return vecEyePosition;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
Vector CPropAPC::BodyTarget( const Vector &posSrc, bool bNoisy )
{
if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE )
{
return WorldSpaceCenter();
}
return BaseClass::BodyTarget( posSrc, bNoisy );
}
//-----------------------------------------------------------------------------
// Add a smoke trail since we've taken more damage
//-----------------------------------------------------------------------------
void CPropAPC::AddSmokeTrail( const Vector &vecPos )
{
// Start this trail out with a bang!
ExplosionCreate( vecPos, vec3_angle, this, 1000, 500.0f, SF_ENVEXPLOSION_NODAMAGE |
SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE |
SF_ENVEXPLOSION_NOFIREBALLSMOKE, 0 );
UTIL_ScreenShake( vecPos, 25.0, 150.0, 1.0, 750.0f, SHAKE_START );
if ( m_nSmokeTrailCount == MAX_SMOKE_TRAILS )
return;
SmokeTrail *pSmokeTrail = SmokeTrail::CreateSmokeTrail();
if( !pSmokeTrail )
return;
// See if there's an attachment for this smoke trail
char buf[32];
Q_snprintf( buf, 32, "damage%d", m_nSmokeTrailCount );
int nAttachment = LookupAttachment( buf );
++m_nSmokeTrailCount;
pSmokeTrail->m_SpawnRate = 4;
pSmokeTrail->m_ParticleLifetime = 5.0f;
pSmokeTrail->m_StartColor.Init( 0.7f, 0.7f, 0.7f );
pSmokeTrail->m_EndColor.Init( 0.6, 0.6, 0.6 );
pSmokeTrail->m_StartSize = 32;
pSmokeTrail->m_EndSize = 64;
pSmokeTrail->m_SpawnRadius = 4;
pSmokeTrail->m_Opacity = 0.5f;
pSmokeTrail->m_MinSpeed = 16;
pSmokeTrail->m_MaxSpeed = 16;
pSmokeTrail->m_MinDirectedSpeed = 16.0f;
pSmokeTrail->m_MaxDirectedSpeed = 16.0f;
pSmokeTrail->SetLifetime( 5 );
pSmokeTrail->SetParent( this, nAttachment );
Vector vecForward( 0, 0, 1 );
QAngle angles;
VectorAngles( vecForward, angles );
if ( nAttachment == 0 )
{
pSmokeTrail->SetAbsOrigin( vecPos );
pSmokeTrail->SetAbsAngles( angles );
}
else
{
pSmokeTrail->SetLocalOrigin( vec3_origin );
pSmokeTrail->SetLocalAngles( angles );
}
pSmokeTrail->SetMoveType( MOVETYPE_NONE );
}
//------------------------------------------------------------------------------
// Pow!
//------------------------------------------------------------------------------
void CPropAPC::ExplodeAndThrowChunk( const Vector &vecExplosionPos )
{
ExplosionCreate( vecExplosionPos, vec3_angle, this, 1000, 500.0f,
SF_ENVEXPLOSION_NODAMAGE | SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS |
SF_ENVEXPLOSION_NOSMOKE | SF_ENVEXPLOSION_NOFIREBALLSMOKE, 0 );
UTIL_ScreenShake( vecExplosionPos, 25.0, 150.0, 1.0, 750.0f, SHAKE_START );
// Drop a flaming, smoking chunk.
CGib *pChunk = CREATE_ENTITY( CGib, "gib" );
pChunk->Spawn( "models/gibs/hgibs.mdl" );
pChunk->SetBloodColor( DONT_BLEED );
QAngle vecSpawnAngles;
vecSpawnAngles.Random( -90, 90 );
pChunk->SetAbsOrigin( vecExplosionPos );
pChunk->SetAbsAngles( vecSpawnAngles );
int nGib = random->RandomInt( 0, APC_MAX_CHUNKS - 1 );
pChunk->Spawn( s_pChunkModelName[nGib] );
pChunk->SetOwnerEntity( this );
pChunk->m_lifeTime = random->RandomFloat( 6.0f, 8.0f );
pChunk->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
IPhysicsObject *pPhysicsObject = pChunk->VPhysicsInitNormal( SOLID_VPHYSICS, pChunk->GetSolidFlags(), false );
// Set the velocity
if ( pPhysicsObject )
{
pPhysicsObject->EnableMotion( true );
Vector vecVelocity;
QAngle angles;
angles.x = random->RandomFloat( -40, 0 );
angles.y = random->RandomFloat( 0, 360 );
angles.z = 0.0f;
AngleVectors( angles, &vecVelocity );
vecVelocity *= random->RandomFloat( 300, 900 );
vecVelocity += GetAbsVelocity();
AngularImpulse angImpulse;
angImpulse = RandomAngularImpulse( -180, 180 );
pChunk->SetAbsVelocity( vecVelocity );
pPhysicsObject->SetVelocity(&vecVelocity, &angImpulse );
}
CEntityFlame *pFlame = CEntityFlame::Create( pChunk, false );
if ( pFlame != NULL )
{
pFlame->SetLifetime( pChunk->m_lifeTime );
}
}
//-----------------------------------------------------------------------------
// Should we trigger a damage effect?
//-----------------------------------------------------------------------------
inline bool CPropAPC::ShouldTriggerDamageEffect( int nPrevHealth, int nEffectCount ) const
{
int nPrevRange = (int)( ((float)nPrevHealth / (float)GetMaxHealth()) * nEffectCount );
int nRange = (int)( ((float)GetHealth() / (float)GetMaxHealth()) * nEffectCount );
return ( nRange != nPrevRange );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropAPC::Event_Killed( const CTakeDamageInfo &info )
{
m_OnDeath.FireOutput( info.GetAttacker(), this );
Vector vecAbsMins, vecAbsMaxs;
CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs );
Vector vecNormalizedMins, vecNormalizedMaxs;
CollisionProp()->WorldToNormalizedSpace( vecAbsMins, &vecNormalizedMins );
CollisionProp()->WorldToNormalizedSpace( vecAbsMaxs, &vecNormalizedMaxs );
Vector vecAbsPoint;
CPASFilter filter( GetAbsOrigin() );
for (int i = 0; i < 5; i++)
{
CollisionProp()->RandomPointInBounds( vecNormalizedMins, vecNormalizedMaxs, &vecAbsPoint );
te->Explosion( filter, random->RandomFloat( 0.0, 1.0 ), &vecAbsPoint,
g_sModelIndexFireball, random->RandomInt( 4, 10 ),
random->RandomInt( 8, 15 ),
( i < 2 ) ? TE_EXPLFLAG_NODLIGHTS : TE_EXPLFLAG_NOPARTICLES | TE_EXPLFLAG_NOFIREBALLSMOKE | TE_EXPLFLAG_NODLIGHTS,
100, 0 );
}
// TODO: make the gibs spawn in sync with the delayed explosions
int nGibs = random->RandomInt( 1, 4 );
for ( int i = 0; i < nGibs; i++)
{
// Throw a flaming, smoking chunk.
CGib *pChunk = CREATE_ENTITY( CGib, "gib" );
pChunk->Spawn( "models/gibs/hgibs.mdl" );
pChunk->SetBloodColor( DONT_BLEED );
QAngle vecSpawnAngles;
vecSpawnAngles.Random( -90, 90 );
pChunk->SetAbsOrigin( vecAbsPoint );
pChunk->SetAbsAngles( vecSpawnAngles );
int nGib = random->RandomInt( 0, APC_MAX_CHUNKS - 1 );
pChunk->Spawn( s_pChunkModelName[nGib] );
pChunk->SetOwnerEntity( this );
pChunk->m_lifeTime = random->RandomFloat( 6.0f, 8.0f );
pChunk->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
IPhysicsObject *pPhysicsObject = pChunk->VPhysicsInitNormal( SOLID_VPHYSICS, pChunk->GetSolidFlags(), false );
// Set the velocity
if ( pPhysicsObject )
{
pPhysicsObject->EnableMotion( true );
Vector vecVelocity;
QAngle angles;
angles.x = random->RandomFloat( -20, 20 );
angles.y = random->RandomFloat( 0, 360 );
angles.z = 0.0f;
AngleVectors( angles, &vecVelocity );
vecVelocity *= random->RandomFloat( 300, 900 );
vecVelocity += GetAbsVelocity();
AngularImpulse angImpulse;
angImpulse = RandomAngularImpulse( -180, 180 );
pChunk->SetAbsVelocity( vecVelocity );
pPhysicsObject->SetVelocity(&vecVelocity, &angImpulse );
}
CEntityFlame *pFlame = CEntityFlame::Create( pChunk, false );
if ( pFlame != NULL )
{
pFlame->SetLifetime( pChunk->m_lifeTime );
}
}
UTIL_ScreenShake( vecAbsPoint, 25.0, 150.0, 1.0, 750.0f, SHAKE_START );
if( hl2_episodic.GetBool() )
{
// EP1 perf hit
Ignite( 6, false );
}
else
{
Ignite( 60, false );
}
m_lifeState = LIFE_DYING;
// Spawn a lesser amount if the player is close
m_iRocketSalvoLeft = DEATH_VOLLEY_ROCKET_COUNT;
m_flRocketTime = gpGlobals->curtime;
}
//-----------------------------------------------------------------------------
// Purpose: Blows it up!
//-----------------------------------------------------------------------------
void CPropAPC::InputDestroy( inputdata_t &inputdata )
{
CTakeDamageInfo info( this, this, m_iHealth, DMG_BLAST );
info.SetDamagePosition( WorldSpaceCenter() );
info.SetDamageForce( Vector( 0, 0, 1 ) );
TakeDamage( info );
}
//-----------------------------------------------------------------------------
// Aim the next rocket at a specific target
//-----------------------------------------------------------------------------
void CPropAPC::InputFireMissileAt( inputdata_t &inputdata )
{
string_t strMissileTarget = MAKE_STRING( inputdata.value.String() );
CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, strMissileTarget, NULL, inputdata.pActivator, inputdata.pCaller );
if ( pTarget == NULL )
{
DevWarning( "%s: Could not find target '%s'!\n", GetClassname(), STRING( strMissileTarget ) );
return;
}
m_hSpecificRocketTarget = pTarget;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CPropAPC::OnTakeDamage( const CTakeDamageInfo &info )
{
if ( m_iHealth == 0 )
return 0;
m_OnDamaged.FireOutput( info.GetAttacker(), this );
if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() )
{
m_OnDamagedByPlayer.FireOutput( info.GetAttacker(), this );
}
CTakeDamageInfo dmgInfo = info;
if ( dmgInfo.GetDamageType() & (DMG_BLAST | DMG_AIRBOAT) )
{
int nPrevHealth = GetHealth();
m_iHealth -= dmgInfo.GetDamage();
if ( m_iHealth <= 0 )
{
m_iHealth = 0;
Event_Killed( dmgInfo );
return 0;
}
// Chain
// BaseClass::OnTakeDamage( dmgInfo );
// Spawn damage effects
if ( nPrevHealth != GetHealth() )
{
if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_SMOKE_TRAILS ) )
{
AddSmokeTrail( dmgInfo.GetDamagePosition() );
}
if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_EXPLOSIONS ) )
{
ExplodeAndThrowChunk( dmgInfo.GetDamagePosition() );
}
}
}
return 1;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pMoveData -
//-----------------------------------------------------------------------------
void CPropAPC::ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMoveData )
{
BaseClass::ProcessMovement( pPlayer, pMoveData );
if ( m_flDangerSoundTime > gpGlobals->curtime )
return;
QAngle vehicleAngles = GetLocalAngles();
Vector vecStart = GetAbsOrigin();
Vector vecDir;
GetVectors( &vecDir, NULL, NULL );
// Make danger sounds ahead of the APC
trace_t tr;
Vector vecSpot, vecLeftDir, vecRightDir;
// lay down sound path
vecSpot = vecStart + vecDir * 600;
CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, 400, 0.1, this );
// put sounds a bit to left and right but slightly closer to APC to make a "cone" of sound
// in front of it
QAngle leftAngles = vehicleAngles;
leftAngles[YAW] += 20;
VehicleAngleVectors( leftAngles, &vecLeftDir, NULL, NULL );
vecSpot = vecStart + vecLeftDir * 400;
UTIL_TraceLine( vecStart, vecSpot, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, 400, 0.1, this );
QAngle rightAngles = vehicleAngles;
rightAngles[YAW] -= 20;
VehicleAngleVectors( rightAngles, &vecRightDir, NULL, NULL );
vecSpot = vecStart + vecRightDir * 400;
UTIL_TraceLine( vecStart, vecSpot, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, 400, 0.1, this);
m_flDangerSoundTime = gpGlobals->curtime + 0.3;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropAPC::Think( void )
{
BaseClass::Think();
SetNextThink( gpGlobals->curtime );
if ( !m_bInitialHandbrake ) // after initial timer expires, set the handbrake
{
m_bInitialHandbrake = true;
m_VehiclePhysics.SetHandbrake( true );
m_VehiclePhysics.Think();
}
StudioFrameAdvance();
if ( IsSequenceFinished() )
{
int iSequence = SelectWeightedSequence( ACT_IDLE );
if ( iSequence > ACTIVITY_NOT_AVAILABLE )
{
SetCycle( 0 );
m_flAnimTime = gpGlobals->curtime;
ResetSequence( iSequence );
ResetClientsideFrame();
}
}
if (m_debugOverlays & OVERLAY_NPC_KILL_BIT)
{
CTakeDamageInfo info( this, this, m_iHealth, DMG_BLAST );
info.SetDamagePosition( WorldSpaceCenter() );
info.SetDamageForce( Vector( 0, 0, 1 ) );
TakeDamage( info );
}
}
//-----------------------------------------------------------------------------
// Aims the secondary weapon at a target
//-----------------------------------------------------------------------------
void CPropAPC::AimSecondaryWeaponAt( CBaseEntity *pTarget )
{
m_hRocketTarget = pTarget;
// Update the rocket target
CreateAPCLaserDot();
if ( m_hRocketTarget )
{
m_hLaserDot->SetAbsOrigin( m_hRocketTarget->BodyTarget( WorldSpaceCenter(), false ) );
}
SetLaserDotTarget( m_hLaserDot, m_hRocketTarget );
EnableLaserDot( m_hLaserDot, m_hRocketTarget != NULL );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropAPC::DriveVehicle( float flFrameTime, CUserCmd *ucmd, int iButtonsDown, int iButtonsReleased )
{
switch( m_lifeState )
{
case LIFE_ALIVE:
{
int iButtons = ucmd->buttons;
if ( iButtons & IN_ATTACK )
{
FireMachineGun();
}
else if ( iButtons & IN_ATTACK2 )
{
FireRocket();
}
}
break;
case LIFE_DYING:
FireDying( );
break;
case LIFE_DEAD:
return;
}
BaseClass::DriveVehicle( flFrameTime, ucmd, iButtonsDown, iButtonsReleased );
}
void CPropAPC::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
BaseClass::Use( pActivator, pCaller, useType, value );
if ( pActivator->IsPlayer() )
{
EmitSound ( "combine.door_lock" );
}
}
//-----------------------------------------------------------------------------
// Primary gun
//-----------------------------------------------------------------------------
void CPropAPC::AimPrimaryWeapon( const Vector &vecWorldTarget )
{
EntityMatrix parentMatrix;
parentMatrix.InitFromEntity( this, m_nMachineGunBaseAttachment );
Vector target = parentMatrix.WorldToLocal( vecWorldTarget );
float quadTarget = target.LengthSqr();
float quadTargetXY = target.x*target.x + target.y*target.y;
// Target is too close! Can't aim at it
if ( quadTarget > m_vecBarrelPos.LengthSqr() )
{
// We're trying to aim the offset barrel at an arbitrary point.
// To calculate this, I think of the target as being on a sphere with
// it's center at the origin of the gun.
// The rotation we need is the opposite of the rotation that moves the target
// along the surface of that sphere to intersect with the gun's shooting direction
// To calculate that rotation, we simply calculate the intersection of the ray
// coming out of the barrel with the target sphere (that's the new target position)
// and use atan2() to get angles
// angles from target pos to center
float targetToCenterYaw = atan2( target.y, target.x );
float centerToGunYaw = atan2( m_vecBarrelPos.y, sqrt( quadTarget - (m_vecBarrelPos.y*m_vecBarrelPos.y) ) );
float targetToCenterPitch = atan2( target.z, sqrt( quadTargetXY ) );
float centerToGunPitch = atan2( -m_vecBarrelPos.z, sqrt( quadTarget - (m_vecBarrelPos.z*m_vecBarrelPos.z) ) );
QAngle angles;
angles.Init( -RAD2DEG(targetToCenterPitch+centerToGunPitch), RAD2DEG( targetToCenterYaw + centerToGunYaw ), 0 );
SetPoseParameter( "vehicle_weapon_yaw", angles.y );
SetPoseParameter( "vehicle_weapon_pitch", angles.x );
StudioFrameAdvance();
float curPitch = GetPoseParameter( "vehicle_weapon_pitch" );
float curYaw = GetPoseParameter( "vehicle_weapon_yaw" );
m_bInFiringCone = (fabs(curPitch - angles.x) < 1e-3) && (fabs(curYaw - angles.y) < 1e-3);
}
else
{
m_bInFiringCone = false;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const char *CPropAPC::GetTracerType( void )
{
return "HelicopterTracer";
}
//-----------------------------------------------------------------------------
// Allows the shooter to change the impact effect of his bullets
//-----------------------------------------------------------------------------
void CPropAPC::DoImpactEffect( trace_t &tr, int nDamageType )
{
UTIL_ImpactTrace( &tr, nDamageType, "HelicopterImpact" );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropAPC::DoMuzzleFlash( void )
{
CEffectData data;
data.m_nEntIndex = entindex();
data.m_nAttachmentIndex = m_nMachineGunMuzzleAttachment;
data.m_flScale = 1.0f;
DispatchEffect( "ChopperMuzzleFlash", data );
BaseClass::DoMuzzleFlash();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropAPC::FireMachineGun( void )
{
if ( m_flMachineGunTime > gpGlobals->curtime )
return;
// If we're still firing the salvo, fire quickly
m_iMachineGunBurstLeft--;
if ( m_iMachineGunBurstLeft > 0 )
{
m_flMachineGunTime = gpGlobals->curtime + MACHINE_GUN_BURST_TIME;
}
else
{
// Reload the salvo
m_iMachineGunBurstLeft = MACHINE_GUN_BURST_SIZE;
m_flMachineGunTime = gpGlobals->curtime + MACHINE_GUN_BURST_PAUSE_TIME;
}
Vector vecMachineGunShootPos;
Vector vecMachineGunDir;
GetAttachment( m_nMachineGunMuzzleAttachment, vecMachineGunShootPos, &vecMachineGunDir );
// Fire the round
int bulletType = GetAmmoDef()->Index("AR2");
FireBullets( 1, vecMachineGunShootPos, vecMachineGunDir, VECTOR_CONE_8DEGREES, MAX_TRACE_LENGTH, bulletType, 1 );
DoMuzzleFlash();
EmitSound( "Weapon_AR2.Single" );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropAPC::GetRocketShootPosition( Vector *pPosition )
{
GetAttachment( m_nRocketAttachment, *pPosition );
}
//-----------------------------------------------------------------------------
// Create a corpse
//-----------------------------------------------------------------------------
void CPropAPC::CreateCorpse( )
{
m_lifeState = LIFE_DEAD;
for ( int i = 0; i < APC_MAX_GIBS; ++i )
{
CPhysicsProp *pGib = assert_cast<CPhysicsProp*>(CreateEntityByName( "prop_physics" ));
pGib->SetAbsOrigin( GetAbsOrigin() );
pGib->SetAbsAngles( GetAbsAngles() );
pGib->SetAbsVelocity( GetAbsVelocity() );
pGib->SetModel( s_pGibModelName[i] );
pGib->Spawn();
pGib->SetMoveType( MOVETYPE_VPHYSICS );
float flMass = pGib->GetMass();
if ( flMass < 200 )
{
Vector vecVelocity;
pGib->GetMassCenter( &vecVelocity );
vecVelocity -= WorldSpaceCenter();
vecVelocity.z = fabs(vecVelocity.z);
VectorNormalize( vecVelocity );
// Apply a force that would make a 100kg mass travel 150 - 300 m/s
float flRandomVel = random->RandomFloat( 150, 300 );
vecVelocity *= (100 * flRandomVel) / flMass;
vecVelocity.z += 100.0f;
AngularImpulse angImpulse = RandomAngularImpulse( -500, 500 );
IPhysicsObject *pObj = pGib->VPhysicsGetObject();
if ( pObj != NULL )
{
pObj->AddVelocity( &vecVelocity, &angImpulse );
}
pGib->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
}
if( hl2_episodic.GetBool() )
{
// EP1 perf hit
pGib->Ignite( 6, false );
}
else
{
pGib->Ignite( 60, false );
}
}
AddSolidFlags( FSOLID_NOT_SOLID );
AddEffects( EF_NODRAW );
UTIL_Remove( this );
}
//-----------------------------------------------------------------------------
// Death volley
//-----------------------------------------------------------------------------
void CPropAPC::FireDying( )
{
if ( m_flRocketTime > gpGlobals->curtime )
return;
Vector vecRocketOrigin;
GetRocketShootPosition( &vecRocketOrigin );
Vector vecDir;
vecDir.Random( -1.0f, 1.0f );
if ( vecDir.z < 0.0f )
{
vecDir.z *= -1.0f;
}
VectorNormalize( vecDir );
Vector vecVelocity;
VectorMultiply( vecDir, ROCKET_SPEED * random->RandomFloat( 0.75f, 1.25f ), vecVelocity );
QAngle angles;
VectorAngles( vecDir, angles );
CAPCMissile *pRocket = (CAPCMissile *) CAPCMissile::Create( vecRocketOrigin, angles, vecVelocity, this );
float flDeathTime = random->RandomFloat( 0.3f, 0.5f );
if ( random->RandomFloat( 0.0f, 1.0f ) < 0.3f )
{
pRocket->ExplodeDelay( flDeathTime );
}
else
{
pRocket->AugerDelay( flDeathTime );
}
// Make erratic firing
m_flRocketTime = gpGlobals->curtime + random->RandomFloat( DEATH_VOLLEY_MIN_FIRE_TIME, DEATH_VOLLEY_MAX_FIRE_TIME );
if ( --m_iRocketSalvoLeft <= 0 )
{
CreateCorpse();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropAPC::FireRocket( void )
{
if ( m_flRocketTime > gpGlobals->curtime )
return;
// If we're still firing the salvo, fire quickly
m_iRocketSalvoLeft--;
if ( m_iRocketSalvoLeft > 0 )
{
m_flRocketTime = gpGlobals->curtime + ROCKET_DELAY_TIME;
}
else
{
// Reload the salvo
m_iRocketSalvoLeft = ROCKET_SALVO_SIZE;
m_flRocketTime = gpGlobals->curtime + random->RandomFloat( ROCKET_MIN_BURST_PAUSE_TIME, ROCKET_MAX_BURST_PAUSE_TIME );
}
Vector vecRocketOrigin;
GetRocketShootPosition( &vecRocketOrigin );
static float s_pSide[] = { 0.966, 0.866, 0.5, -0.5, -0.866, -0.966 };
Vector forward;
GetVectors( &forward, NULL, NULL );
Vector vecDir;
CrossProduct( Vector( 0, 0, 1 ), forward, vecDir );
vecDir.z = 1.0f;
vecDir.x *= s_pSide[m_nRocketSide];
vecDir.y *= s_pSide[m_nRocketSide];
if ( ++m_nRocketSide >= 6 )
{
m_nRocketSide = 0;
}
VectorNormalize( vecDir );
Vector vecVelocity;
VectorMultiply( vecDir, ROCKET_SPEED, vecVelocity );
QAngle angles;
VectorAngles( vecDir, angles );
CAPCMissile *pRocket = (CAPCMissile *)CAPCMissile::Create( vecRocketOrigin, angles, vecVelocity, this );
pRocket->IgniteDelay();
if ( m_hSpecificRocketTarget )
{
pRocket->AimAtSpecificTarget( m_hSpecificRocketTarget );
m_hSpecificRocketTarget = NULL;
}
else if ( m_strMissileHint != NULL_STRING )
{
pRocket->SetGuidanceHint( STRING( m_strMissileHint ) );
}
EmitSound( "PropAPC.FireRocket" );
m_OnFiredMissile.FireOutput( this, this );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CPropAPC::MaxAttackRange() const
{
return ROCKET_ATTACK_RANGE_MAX;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropAPC::OnRestore( void )
{
IServerVehicle *pServerVehicle = GetServerVehicle();
if ( pServerVehicle != NULL )
{
// Restore the passenger information we're holding on to
pServerVehicle->RestorePassengerInfo();
}
}
//========================================================================================================================================
// APC FOUR WHEEL PHYSICS VEHICLE SERVER VEHICLE
//========================================================================================================================================
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAPCFourWheelServerVehicle::NPC_AimPrimaryWeapon( Vector vecTarget )
{
CPropAPC *pAPC = ((CPropAPC*)m_pVehicle);
pAPC->AimPrimaryWeapon( vecTarget );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAPCFourWheelServerVehicle::NPC_AimSecondaryWeapon( Vector vecTarget )
{
// Add some random noise
// Vector vecOffset = vecTarget + RandomVector( -128, 128 );
// ((CPropAPC*)m_pVehicle)->AimSecondaryWeaponAt( vecOffset );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAPCFourWheelServerVehicle::Weapon_PrimaryRanges( float *flMinRange, float *flMaxRange )
{
*flMinRange = MACHINE_GUN_ATTACK_RANGE_MIN;
*flMaxRange = MACHINE_GUN_ATTACK_RANGE_MAX;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAPCFourWheelServerVehicle::Weapon_SecondaryRanges( float *flMinRange, float *flMaxRange )
{
*flMinRange = ROCKET_ATTACK_RANGE_MIN;
*flMaxRange = ROCKET_ATTACK_RANGE_MAX;
}
//-----------------------------------------------------------------------------
// Purpose: Return the time at which this vehicle's primary weapon can fire again
//-----------------------------------------------------------------------------
float CAPCFourWheelServerVehicle::Weapon_PrimaryCanFireAt( void )
{
return ((CPropAPC*)m_pVehicle)->PrimaryWeaponFireTime();
}
//-----------------------------------------------------------------------------
// Purpose: Return the time at which this vehicle's secondary weapon can fire again
//-----------------------------------------------------------------------------
float CAPCFourWheelServerVehicle::Weapon_SecondaryCanFireAt( void )
{
return ((CPropAPC*)m_pVehicle)->SecondaryWeaponFireTime();
}