diff options
Diffstat (limited to 'game/server/hl2/vehicle_apc.cpp')
| -rw-r--r-- | game/server/hl2/vehicle_apc.cpp | 1105 |
1 files changed, 1105 insertions, 0 deletions
diff --git a/game/server/hl2/vehicle_apc.cpp b/game/server/hl2/vehicle_apc.cpp new file mode 100644 index 0000000..80c4291 --- /dev/null +++ b/game/server/hl2/vehicle_apc.cpp @@ -0,0 +1,1105 @@ +//========= 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(); +} + |