diff options
Diffstat (limited to 'game/server/hl2/vehicle_airboat.cpp')
| -rw-r--r-- | game/server/hl2/vehicle_airboat.cpp | 2101 |
1 files changed, 2101 insertions, 0 deletions
diff --git a/game/server/hl2/vehicle_airboat.cpp b/game/server/hl2/vehicle_airboat.cpp new file mode 100644 index 0000000..c8a49dd --- /dev/null +++ b/game/server/hl2/vehicle_airboat.cpp @@ -0,0 +1,2101 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "vehicle_base.h" +#include "engine/IEngineSound.h" +#include "in_buttons.h" +#include "ammodef.h" +#include "IEffects.h" +#include "beam_shared.h" +#include "weapon_gauss.h" +#include "soundenvelope.h" +#include "decals.h" +#include "soundent.h" +#include "te_effect_dispatch.h" +#include "physics_saverestore.h" +#include "movevars_shared.h" +#include "npc_attackchopper.h" +#include "weapon_rpg.h" +#include "vphysics/constraints.h" +#include "world.h" +#include "rumble_shared.h" +// NVNT for airboat weapon fire +#include "haptics/haptic_utils.h" +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern ConVar sv_vehicle_autoaim_scale; + +#define VEHICLE_HITBOX_DRIVER 1 + +// +// Body groups. +// +#define AIRBOAT_BODYGROUP_GUN 1 +#define AIRBOAT_BODYGROUP_PROP 2 +#define AIRBOAT_BODYGROUP_BLUR 3 + +#define AIRBOAT_LOCK_SPEED 10 // Airboat must be going slower than this for player to enter or exit, in in/sec + +#define AIRBOAT_DELTA_LENGTH_MAX 12.0f // 1 foot +#define AIRBOAT_FRAMETIME_MIN 1e-6 + +#define AIRBOAT_SPLASH_RIPPLE 0 +#define AIRBOAT_SPLASH_SPRAY 1 +#define AIRBOAT_SPLASH_RIPPLE_SIZE 20.0f + +// +// Pose parameters. +// +#define AIRBOAT_GUN_YAW "vehicle_weapon_yaw" +#define AIRBOAT_GUN_PITCH "vehicle_weapon_pitch" +#define AIRBOAT_FRAME_FLEX_LEFT "Frame_Flex_L" +#define AIRBOAT_FRAME_FLEX_RIGHT "Frame_Flex_R" + +#define CANNON_MAX_UP_PITCH 60.0f +#define CANNON_MAX_DOWN_PITCH 30.0f +#define CANNON_MAX_RIGHT_YAW 165.0f +#define CANNON_MAX_LEFT_YAW 75.0f + +#define CANNON_HEAVY_SHOT_INTERVAL 0.2f +#define CANNON_SHAKE_INTERVAL 1.0f + +static ConVar sk_airboat_max_ammo("sk_airboat_max_ammo", "100" ); +static ConVar sk_airboat_recharge_rate("sk_airboat_recharge_rate", "15" ); +static ConVar sk_airboat_drain_rate("sk_airboat_drain_rate", "10" ); +static ConVar hud_airboathint_numentries( "hud_airboathint_numentries", "10", FCVAR_NONE ); +static ConVar airboat_fatal_stress( "airboat_fatal_stress", "5000", FCVAR_NONE, "Amount of stress in kg that would kill the airboat driver." ); + +extern ConVar autoaim_max_dist; + +class CPropAirboat : public CPropVehicleDriveable +{ + DECLARE_CLASS( CPropAirboat, CPropVehicleDriveable ); + +public: + + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + + // CPropVehicle + virtual void ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMoveData ); + virtual void DriveVehicle( float flFrameTime, CUserCmd *ucmd, int iButtonsDown, int iButtonsReleased ); + void DampenEyePosition( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles ); + bool ShouldThink() { return true; } + + // CBaseEntity + void Think(void); + void Precache( void ); + void Spawn( void ); + virtual void OnRestore(); + virtual void Activate(); + virtual void UpdateOnRemove(); + virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() | FCAP_USE_IN_RADIUS; }; + virtual void DoMuzzleFlash( void ); + virtual void StopLoopingSounds(); + + // position to shoot at + virtual Vector BodyTarget( const Vector &posSrc, bool bNoisy ); + virtual Vector GetSmoothedVelocity( void ); + + virtual void EnterVehicle( CBaseCombatCharacter *pPlayer ); + + virtual bool AllowBlockedExit( CBaseCombatCharacter *pPlayer, int nRole ) { return false; } + virtual void PreExitVehicle( CBaseCombatCharacter *pPlayer, int nRole ); + virtual void ExitVehicle( int nRole ); + + void ComputePDControllerCoefficients( float *pCoefficientsOut, float flFrequency, float flDampening, float flDeltaTime ); + void DampenForwardMotion( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles, float flFrameTime ); + void DampenUpMotion( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles, float flFrameTime ); + + virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ); + virtual int OnTakeDamage( const CTakeDamageInfo &info ); + + void VPhysicsUpdate( IPhysicsObject *pPhysics ); + + // Scraping noises for the various things we drive on. + virtual void VPhysicsFriction( IPhysicsObject *pObject, float energy, int surfaceProps, int surfacePropsHit ); + + bool HeadlightIsOn( void ) { return m_bHeadlightIsOn; } + void HeadlightTurnOn( void ); + void HeadlightTurnOff( void ); + + virtual bool ShouldDrawWaterImpacts( void ); + + bool ShouldForceExit() { return m_bForcedExit; } + void ClearForcedExit() { m_bForcedExit = false; } + + // Input handlers. + void InputWake( inputdata_t &inputdata ); + void InputExitVehicle( inputdata_t &inputdata ); + void InputEnableGun( inputdata_t &inputdata ); + void InputStartRotorWashForces( inputdata_t &inputdata ); + void InputStopRotorWashForces( inputdata_t &inputdata ); + + // Allows the shooter to change the impact effect of his bullets + virtual void DoImpactEffect( trace_t &tr, int nDamageType ); + + // Airboat passengers do not directly receive damage from blasts or radiation damage + virtual bool PassengerShouldReceiveDamage( CTakeDamageInfo &info ) + { + if ( info.GetDamageType() & DMG_VEHICLE ) + return true; + + return (info.GetDamageType() & (DMG_RADIATION|DMG_BLAST|DMG_CRUSH) ) == 0; + } + + const char *GetTracerType( void ); + +private: + + void CreateAntiFlipConstraint(); + + void ApplyStressDamage( IPhysicsObject *pPhysics ); + float CalculatePhysicsStressDamage( vphysics_objectstress_t *pStressOut, IPhysicsObject *pPhysics ); + + void CreateDangerSounds( void ); + + void FireGun( ); + + void UpdateSplashEffects( void ); + void CreateSplash( int nSplashType ); + + // Purpose: Aim Gun at a target + void AimGunAt( const Vector &endPos, float flInterval ); + + // Purpose: Returns the direction the gun is currently aiming at + void GetGunAimDirection( Vector *resultDir ); + + // Recharges the ammo based on speed + void RechargeAmmo(); + + // Removes the ammo... + void RemoveAmmo( float flAmmoAmount ); + + // Purpose: + void ComputeAimPoint( Vector *pVecAimPoint ); + + // Do the right thing for the gun + void UpdateGunState( CUserCmd *ucmd ); + + // Sound management + void CreateSounds(); + void UpdateSound(); + void UpdateWeaponSound(); + void UpdateEngineSound( CSoundEnvelopeController &controller, float speedRatio ); + void UpdateFanSound( CSoundEnvelopeController &controller, float speedRatio ); + void UpdateWaterSound( CSoundEnvelopeController &controller, float speedRatio ); + + void UpdatePropeller(); + void UpdateGauge(); + + void CreatePlayerBlocker(); + void DestroyPlayerBlocker(); + void EnablePlayerBlocker( bool bEnable ); + +private: + + enum + { + GUN_STATE_IDLE = 0, + GUN_STATE_FIRING, + }; + + Vector m_vecLastEyePos; + Vector m_vecLastEyeTarget; + Vector m_vecEyeSpeed; + + //float m_flHandbrakeTime; // handbrake after the fact to keep vehicles from rolling + //bool m_bInitialHandbrake; + + bool m_bForcedExit; + + int m_nGunRefAttachment; + int m_nGunBarrelAttachment; + float m_aimYaw; + float m_aimPitch; + float m_flChargeRemainder; + float m_flDrainRemainder; + int m_nGunState; + float m_flNextHeavyShotTime; + float m_flNextGunShakeTime; + + CNetworkVar( int, m_nAmmoCount ); + CNetworkVar( bool, m_bHeadlightIsOn ); + EHANDLE m_hAvoidSphere; + + int m_nSplashAttachment; + + float m_flPrevThrottle; // Throttle during last think. Used for detecting state changes. + float m_flSpinRate; // Current rate of spin of propeller: 0 = min, 1.0 = max + float m_flTargetSpinRate; // Target rate of spin of propeller: 0 = min, 1.0 = max + float m_flPropTime; // Time to turn on/off the prop. + float m_flBlurTime; // Time to turn on/off the blur. + + CSoundPatch *m_pFanSound; + CSoundPatch *m_pFanMaxSpeedSound; + CSoundPatch *m_pEngineSound; + CSoundPatch *m_pWaterFastSound; + CSoundPatch *m_pWaterStoppedSound; + CSoundPatch *m_pGunFiringSound; + + float m_flEngineIdleTime; // Time to start playing the engine's idle sound. + float m_flEngineDuckTime; // Time to reduce the volume of the engine's idle sound. + + bool m_bFadeOutFan; // Fade out fan sound after cruising at max speed for a while. + + int m_nPrevWaterLevel; // Used for detecting transitions into/out of water. + float m_flWaterStoppedPitchTime; // Time to pitch shift the water stopped sound. + + float m_flLastImpactEffectTime; + int m_iNumberOfEntries; + + IPhysicsConstraint *m_pAntiFlipConstraint; // A ragdoll constraint that prevents us from flipping. + + CHandle<CEntityBlocker> m_hPlayerBlocker; + + CNetworkVar( Vector, m_vecPhysVelocity ); + + CNetworkVar( int, m_nExactWaterLevel ); + + IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_nWaterLevel ); + +}; + +IMPLEMENT_SERVERCLASS_ST( CPropAirboat, DT_PropAirboat ) + SendPropBool( SENDINFO( m_bHeadlightIsOn ) ), + SendPropInt( SENDINFO( m_nAmmoCount ), 9 ), + SendPropInt( SENDINFO( m_nExactWaterLevel ) ), + SendPropInt( SENDINFO( m_nWaterLevel ) ), + SendPropVector( SENDINFO( m_vecPhysVelocity ) ), +END_SEND_TABLE(); + +LINK_ENTITY_TO_CLASS( prop_vehicle_airboat, CPropAirboat ); + +BEGIN_DATADESC( CPropAirboat ) + DEFINE_FIELD( m_vecLastEyePos, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_vecLastEyeTarget, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_vecEyeSpeed, FIELD_VECTOR ), + +// DEFINE_FIELD( m_flHandbrakeTime, FIELD_TIME ), +// DEFINE_FIELD( m_bInitialHandbrake,FIELD_BOOLEAN ), +// DEFINE_FIELD( m_nGunRefAttachment, FIELD_INTEGER ), +// DEFINE_FIELD( m_nGunBarrelAttachment, FIELD_INTEGER ), + DEFINE_FIELD( m_aimYaw, FIELD_FLOAT ), + DEFINE_FIELD( m_aimPitch, FIELD_FLOAT ), + DEFINE_FIELD( m_flChargeRemainder, FIELD_FLOAT ), + DEFINE_FIELD( m_flDrainRemainder, FIELD_FLOAT ), + DEFINE_FIELD( m_nGunState, FIELD_INTEGER ), + DEFINE_FIELD( m_flNextHeavyShotTime, FIELD_TIME ), + DEFINE_FIELD( m_flNextGunShakeTime, FIELD_TIME ), + DEFINE_FIELD( m_nAmmoCount, FIELD_INTEGER ), + DEFINE_FIELD( m_bHeadlightIsOn, FIELD_BOOLEAN ), + DEFINE_FIELD( m_hAvoidSphere, FIELD_EHANDLE ), +// DEFINE_FIELD( m_nSplashAttachment, FIELD_INTEGER ), + DEFINE_FIELD( m_hPlayerBlocker, FIELD_EHANDLE ), + + DEFINE_FIELD( m_vecPhysVelocity, FIELD_VECTOR ), + DEFINE_FIELD( m_nExactWaterLevel, FIELD_INTEGER ), + + DEFINE_FIELD( m_flPrevThrottle, FIELD_FLOAT ), + DEFINE_FIELD( m_flSpinRate, FIELD_FLOAT ), + DEFINE_FIELD( m_flTargetSpinRate, FIELD_FLOAT ), + DEFINE_FIELD( m_flPropTime, FIELD_TIME ), + DEFINE_FIELD( m_flBlurTime, FIELD_TIME ), + DEFINE_FIELD( m_bForcedExit, FIELD_BOOLEAN ), + + DEFINE_SOUNDPATCH( m_pFanSound ), + DEFINE_SOUNDPATCH( m_pFanMaxSpeedSound ), + DEFINE_SOUNDPATCH( m_pEngineSound ), + DEFINE_SOUNDPATCH( m_pWaterFastSound ), + DEFINE_SOUNDPATCH( m_pWaterStoppedSound ), + DEFINE_SOUNDPATCH( m_pGunFiringSound ), + + DEFINE_PHYSPTR( m_pAntiFlipConstraint ), + + DEFINE_FIELD( m_flEngineIdleTime, FIELD_TIME ), + DEFINE_FIELD( m_flEngineDuckTime, FIELD_TIME ), + DEFINE_FIELD( m_bFadeOutFan, FIELD_BOOLEAN ), + + DEFINE_FIELD( m_nPrevWaterLevel, FIELD_INTEGER ), + DEFINE_FIELD( m_flWaterStoppedPitchTime, FIELD_TIME ), + + DEFINE_FIELD( m_flLastImpactEffectTime, FIELD_TIME ), + DEFINE_FIELD( m_iNumberOfEntries, FIELD_INTEGER ), + + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "EnableGun", InputEnableGun ), + DEFINE_INPUTFUNC( FIELD_VOID, "StartRotorWashForces", InputStartRotorWashForces ), + DEFINE_INPUTFUNC( FIELD_VOID, "StopRotorWashForces", InputStopRotorWashForces ), + DEFINE_INPUTFUNC( FIELD_VOID, "ExitVehicle", InputExitVehicle ), + DEFINE_INPUTFUNC( FIELD_VOID, "Wake", InputWake ), + +END_DATADESC() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::Precache( void ) +{ + BaseClass::Precache(); + + PrecacheScriptSound( "Airboat_engine_stop" ); + PrecacheScriptSound( "Airboat_engine_start" ); + + PrecacheScriptSound( "Airboat.FireGunHeavy" ); + PrecacheScriptSound( "Airboat.FireGunRevDown"); + + PrecacheScriptSound( "Airboat_engine_idle" ); + PrecacheScriptSound( "Airboat_engine_fullthrottle" ); + PrecacheScriptSound( "Airboat_fan_idle" ); + PrecacheScriptSound( "Airboat_fan_fullthrottle" ); + PrecacheScriptSound( "Airboat_water_stopped" ); + PrecacheScriptSound( "Airboat_water_fast" ); + PrecacheScriptSound( "Airboat_impact_splash" ); + PrecacheScriptSound( "Airboat_impact_hard" ); + + PrecacheScriptSound( "Airboat_headlight_on" ); + PrecacheScriptSound( "Airboat_headlight_off" ); + + PrecacheScriptSound( "Airboat.FireGunLoop" ); + + PrecacheMaterial( "effects/splashwake1" ); + PrecacheMaterial( "effects/splashwake4" ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::Spawn( void ) +{ + m_nAmmoCount = m_bHasGun ? 0 : -1; + m_hAvoidSphere = CreateHelicopterAvoidanceSphere( this, 0, 50.0f, false ); + m_flLastImpactEffectTime = -1; + m_iNumberOfEntries = 0; + + // Setup vehicle as a ray-cast airboat. + SetVehicleType( VEHICLE_TYPE_AIRBOAT_RAYCAST ); + SetCollisionGroup( COLLISION_GROUP_VEHICLE ); + BaseClass::Spawn(); + + AddSolidFlags( FSOLID_NOT_STANDABLE ); + SetAnimatedEveryTick( true ); + + // Handbrake data. + //m_flHandbrakeTime = gpGlobals->curtime + 0.1; + //m_bInitialHandbrake = false; + m_VehiclePhysics.SetHasBrakePedal( false ); + + m_flMinimumSpeedToEnterExit = AIRBOAT_LOCK_SPEED; + + m_takedamage = DAMAGE_EVENTS_ONLY; + + SetBodygroup(AIRBOAT_BODYGROUP_GUN, m_bHasGun); + SetBodygroup(AIRBOAT_BODYGROUP_PROP, true); + + SetPoseParameter( AIRBOAT_GUN_YAW, 0 ); + SetPoseParameter( AIRBOAT_GUN_PITCH, 0 ); + SetPoseParameter( AIRBOAT_FRAME_FLEX_LEFT, 0 ); + SetPoseParameter( AIRBOAT_FRAME_FLEX_RIGHT, 0 ); + + m_aimYaw = 0; + m_aimPitch = 0; + m_bUnableToFire = true; + m_nGunState = GUN_STATE_IDLE; + + SetPoseParameter( "Steer_Shock", 0.0f ); + + // Get the physics object so we can adjust the buoyancy. + IPhysicsObject *pPhysAirboat = VPhysicsGetObject(); + if ( pPhysAirboat ) + { + pPhysAirboat->SetBuoyancyRatio( 0.0f ); + PhysSetGameFlags( pPhysAirboat, FVPHYSICS_HEAVY_OBJECT ); + } + + //CreateAntiFlipConstraint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Create a ragdoll constraint that prevents us from flipping. +//----------------------------------------------------------------------------- +void CPropAirboat::CreateAntiFlipConstraint() +{ + constraint_ragdollparams_t ragdoll; + ragdoll.Defaults(); + + // Don't prevent the boat from moving, just flipping. + ragdoll.onlyAngularLimits = true; + + // Put the ragdoll constraint in the space of the airboat. + SetIdentityMatrix( ragdoll.constraintToAttached ); + BuildObjectRelativeXform( g_PhysWorldObject, VPhysicsGetObject(), ragdoll.constraintToReference ); + + ragdoll.axes[0].minRotation = -100; + ragdoll.axes[0].maxRotation = 100; + ragdoll.axes[1].minRotation = -100; + ragdoll.axes[1].maxRotation = 100; + ragdoll.axes[2].minRotation = -180; + ragdoll.axes[2].maxRotation = 180; + + m_pAntiFlipConstraint = physenv->CreateRagdollConstraint( g_PhysWorldObject, VPhysicsGetObject(), NULL, ragdoll ); + + //NDebugOverlay::Cross3DOriented( ragdoll.constraintToReference, 128, 255, true, 100 ); +} + + +//----------------------------------------------------------------------------- +// Attachment indices +//----------------------------------------------------------------------------- +void CPropAirboat::UpdateOnRemove() +{ + BaseClass::UpdateOnRemove(); + + if ( m_hAvoidSphere ) + { + UTIL_Remove( m_hAvoidSphere ); + m_hAvoidSphere = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Attachment indices +//----------------------------------------------------------------------------- +void CPropAirboat::Activate() +{ + BaseClass::Activate(); + + m_nGunRefAttachment = LookupAttachment( "gun" ); + m_nGunBarrelAttachment = LookupAttachment( "muzzle" ); + m_nSplashAttachment = LookupAttachment( "splash_pt" ); + + CreateSounds(); + + CBaseServerVehicle *pServerVehicle = dynamic_cast<CBaseServerVehicle *>(GetServerVehicle()); + if ( pServerVehicle ) + { + if( pServerVehicle->GetPassenger() ) + { + // If a boat comes back from a save game with a driver, make sure the engine rumble starts up. + pServerVehicle->StartEngineRumble(); + } + } + + //CreatePlayerBlocker(); + //EnablePlayerBlocker( true ); +} + + +void CPropAirboat::CreatePlayerBlocker() +{ + Assert( m_hPlayerBlocker == NULL ); + DestroyPlayerBlocker(); + + m_hPlayerBlocker = CEntityBlocker::Create( GetAbsOrigin(), Vector( -84, -32, 0 ), Vector( 54, 32, 84 ), this, false ); + if ( m_hPlayerBlocker != NULL ) + { + m_hPlayerBlocker->SetParent( this ); + m_hPlayerBlocker->SetLocalOrigin( vec3_origin ); + m_hPlayerBlocker->SetLocalAngles( vec3_angle ); + m_hPlayerBlocker->SetCollisionGroup( COLLISION_GROUP_PLAYER ); + m_hPlayerBlocker->AddSolidFlags( FSOLID_NOT_SOLID ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::DestroyPlayerBlocker() +{ + if ( m_hPlayerBlocker != NULL ) + { + UTIL_Remove( m_hPlayerBlocker ); + } + + m_hPlayerBlocker = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bEnable - +//----------------------------------------------------------------------------- +void CPropAirboat::EnablePlayerBlocker( bool bEnable ) +{ + if ( m_hPlayerBlocker != NULL ) + { + if ( bEnable ) + { + m_hPlayerBlocker->RemoveSolidFlags( FSOLID_NOT_SOLID ); + } + else + { + m_hPlayerBlocker->AddSolidFlags( FSOLID_NOT_SOLID ); + } + } +} + + +//----------------------------------------------------------------------------- +// Update the weapon sounds +//----------------------------------------------------------------------------- +#define MIN_CHARGE_SOUND 0.4f +#define MIN_PITCH_CHANGE ( MIN_CHARGE_SOUND + ( ( 1.0f - MIN_CHARGE_SOUND ) / 3.0f ) ) +#define VOLUME_CHANGE_TIME 0.5f + +void CPropAirboat::UpdateWeaponSound() +{ + if ( HasGun() ) + { + CSoundEnvelopeController *pController = &CSoundEnvelopeController::GetController(); + float flVolume = pController->SoundGetVolume( m_pGunFiringSound ); + if ( (m_nGunState == GUN_STATE_IDLE) || (m_nAmmoCount == 0) ) + { + if ( flVolume != 0.0f ) + { + pController->SoundChangeVolume( m_pGunFiringSound, 0.0f, 0.01f ); + } + } + else + { + if ( flVolume != 1.0f ) + { + pController->SoundChangeVolume( m_pGunFiringSound, 1.0f, 0.01f ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Force the player to exit the vehicle. +//----------------------------------------------------------------------------- +void CPropAirboat::InputExitVehicle( inputdata_t &inputdata ) +{ + m_bForcedExit = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Force the airboat to wake up. This was needed to fix a last-minute +// bug for the XBox -- the airboat didn't fall with the platform +// in d1_canals_10b. +//----------------------------------------------------------------------------- +void CPropAirboat::InputWake( inputdata_t &inputdata ) +{ + VPhysicsGetObject()->Wake(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler to enable or disable the airboat's mounted gun. +//----------------------------------------------------------------------------- +void CPropAirboat::InputEnableGun( inputdata_t &inputdata ) +{ + m_bHasGun = inputdata.value.Bool(); + SetBodygroup(AIRBOAT_BODYGROUP_GUN, m_bHasGun); + + // When enabling the gun, give full ammo + if ( m_bHasGun ) + { + m_nAmmoCount = sk_airboat_max_ammo.GetInt(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler to enable or disable the airboat's mounted gun. +//----------------------------------------------------------------------------- +void CPropAirboat::InputStartRotorWashForces( inputdata_t &inputdata ) +{ + RemoveEFlags( EFL_NO_ROTORWASH_PUSH ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler to enable or disable the airboat's mounted gun. +//----------------------------------------------------------------------------- +void CPropAirboat::InputStopRotorWashForces( inputdata_t &inputdata ) +{ + AddEFlags( EFL_NO_ROTORWASH_PUSH ); +} + + +//----------------------------------------------------------------------------- +// Creating vphysics +//----------------------------------------------------------------------------- +void CPropAirboat::OnRestore() +{ + BaseClass::OnRestore(); + + IPhysicsObject *pPhysAirboat = VPhysicsGetObject(); + if ( pPhysAirboat ) + { + pPhysAirboat->SetBuoyancyRatio( 0.0f ); + PhysSetGameFlags( pPhysAirboat, FVPHYSICS_HEAVY_OBJECT ); + } + + // If the player's in the vehicle, NPCs should ignore it + if ( GetDriver() ) + { + SetNavIgnore(); + } +} + + +//----------------------------------------------------------------------------- +// Used for navigation +//----------------------------------------------------------------------------- +void CPropAirboat::EnterVehicle( CBaseCombatCharacter *pPlayer ) +{ + BaseClass::EnterVehicle( pPlayer ); + + //EnablePlayerBlocker( false ); + + // NPCs like manhacks should try to hit us + SetNavIgnore(); + + // Play the engine start sound. + float flDuration; + EmitSound( "Airboat_engine_start", 0.0, &flDuration ); + m_VehiclePhysics.TurnOn(); + + // Start playing the engine's idle sound as the startup sound finishes. + m_flEngineIdleTime = gpGlobals->curtime + flDuration - 0.1; +} + + +//----------------------------------------------------------------------------- +// Purpose: Called when exiting, just before playing the exit animation. +//----------------------------------------------------------------------------- +void CPropAirboat::PreExitVehicle( CBaseCombatCharacter *pPlayer, int nRole ) +{ + if ( HeadlightIsOn() ) + { + HeadlightTurnOff(); + } + + // Stop shooting. + m_nGunState = GUN_STATE_IDLE; + + CBaseEntity *pDriver = GetDriver(); + CBasePlayer *pPlayerDriver; + if( pDriver && pDriver->IsPlayer() ) + { + pPlayerDriver = dynamic_cast<CBasePlayer*>(pDriver); + if( pPlayerDriver ) + { + pPlayerDriver->RumbleEffect( RUMBLE_AIRBOAT_GUN, 0, RUMBLE_FLAG_STOP ); + } + } + + BaseClass::PreExitVehicle( pPlayer, nRole ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called when exiting, after completing the exit animation. +// Input : iRole - +//----------------------------------------------------------------------------- +void CPropAirboat::ExitVehicle( int nRole ) +{ + CBaseEntity *pDriver = GetDriver(); + + //EnablePlayerBlocker( true ); + + BaseClass::ExitVehicle( nRole ); + + if (!pDriver) + return; + +#if 0 + // On ORANGE BOX this is causing a big blank box to show up, which is worse + // than the HUD hint persisting for a little while, so don't do it. (sjb) + // clear the hint + UTIL_HudHintText( pDriver, "" ); +#endif + + // NPCs like manhacks should try to avoid us again + ClearNavIgnore(); + + // Play the engine shutoff sound. + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + CPASAttenuationFilter filter( this ); + + EmitSound_t ep; + ep.m_nChannel = CHAN_BODY; + ep.m_pSoundName = "Airboat_engine_stop"; + ep.m_flVolume = controller.SoundGetVolume( m_pEngineSound ); + ep.m_SoundLevel = SNDLVL_NORM; + ep.m_nPitch = controller.SoundGetPitch( m_pEngineSound ); + + EmitSound( filter, entindex(), ep ); + m_VehiclePhysics.TurnOff(); + + // Shut off the airboat sounds. + controller.SoundChangeVolume( m_pEngineSound, 0.0, 0.0 ); + controller.SoundChangeVolume( m_pFanSound, 0.0, 0.0 ); + controller.SoundChangeVolume( m_pFanMaxSpeedSound, 0.0, 0.0 ); + controller.SoundChangeVolume( m_pWaterStoppedSound, 0.0, 0.0 ); + controller.SoundChangeVolume( m_pWaterFastSound, 0.0, 0.0 ); + controller.SoundChangeVolume( m_pGunFiringSound, 0.0, 0.0 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::HeadlightTurnOn( void ) +{ + EmitSound( "Airboat_headlight_on" ); + m_bHeadlightIsOn = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::HeadlightTurnOff( void ) +{ + EmitSound( "Airboat_headlight_off" ); + m_bHeadlightIsOn = false; +} + + +//----------------------------------------------------------------------------- +// position to shoot at +//----------------------------------------------------------------------------- +Vector CPropAirboat::BodyTarget( const Vector &posSrc, bool bNoisy ) +{ + Vector vecPosition; + QAngle angles; + if ( GetServerVehicle()->GetPassenger() ) + { + // FIXME: Reconcile this with other functions that store a cached version of the results here? + GetServerVehicle()->GetVehicleViewPosition( VEHICLE_ROLE_DRIVER, &vecPosition, &angles ); + } + else + { + vecPosition = WorldSpaceCenter(); + } + return vecPosition; +} + + +//----------------------------------------------------------------------------- +// Smoothed velocity +//----------------------------------------------------------------------------- +#define SMOOTHED_MIN_VELOCITY 75.0f +#define SMOOTHED_MAX_VELOCITY 150.0f + +Vector CPropAirboat::GetSmoothedVelocity( void ) +{ + // If we're going too slow, return the forward direction as the velocity + // for NPC prediction purposes + Vector vecSmoothedVelocity = BaseClass::GetSmoothedVelocity(); + float flSpeed = vecSmoothedVelocity.Length(); + if ( flSpeed >= SMOOTHED_MAX_VELOCITY ) + return vecSmoothedVelocity; + + Vector vecForward; + GetVectors( &vecForward, NULL, NULL ); + vecForward *= MAX( flSpeed, 1.0f ); + if ( flSpeed <= SMOOTHED_MIN_VELOCITY ) + return vecForward; + + float flBlend = SimpleSplineRemapVal( flSpeed, SMOOTHED_MIN_VELOCITY, SMOOTHED_MAX_VELOCITY, 0.0f, 1.0f ); + VectorLerp( vecForward, vecSmoothedVelocity, flBlend, vecSmoothedVelocity ); + return vecSmoothedVelocity; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) +{ + CTakeDamageInfo info = inputInfo; + if ( ptr->hitbox != VEHICLE_HITBOX_DRIVER ) + { + if ( inputInfo.GetDamageType() & DMG_BULLET ) + { + info.ScaleDamage( 0.0001 ); + } + } + + BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &info - +// &vecEnd - +// *pTraceFilter - +// *pVecTracerDest - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CPropAirboat::ShouldDrawWaterImpacts( void ) +{ + // The airboat spits out so much crap that we need to do cheaper versions + // of the impact effects. Also, we need to do less of them. + if ( m_flLastImpactEffectTime >= gpGlobals->curtime ) + return false; + + m_flLastImpactEffectTime = gpGlobals->curtime + 0.05f; + + return true; +} + +//----------------------------------------------------------------------------- +// Allows the shooter to change the impact effect of his bullets +//----------------------------------------------------------------------------- +void CPropAirboat::DoImpactEffect( trace_t &tr, int nDamageType ) +{ + // The airboat spits out so much crap that we need to do cheaper versions + // of the impact effects. Also, we need to do less of them. + if ( m_flLastImpactEffectTime == gpGlobals->curtime ) + return; + + // Randomly drop out + if ( random->RandomInt( 0, 5 ) ) + return; + + m_flLastImpactEffectTime = gpGlobals->curtime; + UTIL_ImpactTrace( &tr, nDamageType, "AirboatGunImpact" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CPropAirboat::OnTakeDamage( const CTakeDamageInfo &info ) +{ + // Do scaled up physics damage to the airboat + CTakeDamageInfo physDmg = info; + physDmg.ScaleDamage( 5 ); + if ( physDmg.GetDamageType() & DMG_BLAST ) + { + physDmg.SetDamageForce( info.GetDamageForce() * 10 ); + } + VPhysicsTakeDamage( physDmg ); + + // Check to do damage to driver + if ( m_hPlayer != NULL ) + { + // Don't pass along physics damage + if ( info.GetDamageType() & (DMG_CRUSH|DMG_RADIATION) ) + return 0; + + // Take the damage (strip out the DMG_BLAST) + CTakeDamageInfo playerDmg = info; + + // Mark that we're passing it to the player so the base player accepts the damage + playerDmg.SetDamageType( info.GetDamageType() | DMG_VEHICLE ); + + // Deal the damage to the passenger + m_hPlayer->TakeDamage( playerDmg ); + } + + return 0; +} + + +//----------------------------------------------------------------------------- +// Scraping noises for the various things we drive on. +//----------------------------------------------------------------------------- +void CPropAirboat::VPhysicsFriction( IPhysicsObject *pObject, float energy, int surfaceProps, int surfacePropsHit ) +{ + // don't make noise for hidden/invisible/sky materials + const surfacedata_t *phit = physprops->GetSurfaceData( surfacePropsHit ); + const surfacedata_t *pprops = physprops->GetSurfaceData( surfaceProps ); + if ( phit->game.material == 'X' || pprops->game.material == 'X' ) + return; + + // FIXME: Make different scraping sounds here + float flVolume = 0.3f; + + surfacedata_t *psurf = physprops->GetSurfaceData( surfaceProps ); + const char *pSoundName = physprops->GetString( psurf->sounds.scrapeRough ); + + PhysFrictionSound( this, pObject, pSoundName, psurf->soundhandles.scrapeRough, flVolume ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Aim Gun at a target position. +//----------------------------------------------------------------------------- +// This fixes an optimizer bug that was causing targetYaw and targetPitch to +// always be reported as clamped, thus disabling the gun. Ack! +#pragma optimize("", off) +void CPropAirboat::AimGunAt( const Vector &aimPos, float flInterval ) +{ + matrix3x4_t gunMatrix; + GetAttachment( m_nGunRefAttachment, gunMatrix ); + + // transform the target position into gun space + Vector localTargetPosition; + VectorITransform( aimPos, gunMatrix, localTargetPosition ); + VectorNormalize( localTargetPosition ); + m_bUnableToFire = false; + m_vecGunCrosshair = aimPos; + + // do a look at in gun space (essentially a delta-lookat) + QAngle localTargetAngles; + VectorAngles( localTargetPosition, localTargetAngles ); + + // convert to +/- 180 degrees + localTargetAngles.x = UTIL_AngleDiff( localTargetAngles.x, 0 ); + localTargetAngles.y = UTIL_AngleDiff( localTargetAngles.y, 0 ); + + float targetYaw = m_aimYaw + localTargetAngles.y; + float targetPitch = m_aimPitch + localTargetAngles.x; + + // Constrain our angles + float newTargetYaw = clamp( targetYaw, -CANNON_MAX_RIGHT_YAW, CANNON_MAX_LEFT_YAW ); + float newTargetPitch = clamp( targetPitch, -CANNON_MAX_UP_PITCH, CANNON_MAX_DOWN_PITCH ); + + // If the angles have been clamped, we're looking outside of our valid range + if ( ( newTargetYaw != targetYaw ) || ( newTargetPitch != targetPitch ) ) + { + m_bUnableToFire = true; + } + + targetYaw = newTargetYaw; + targetPitch = newTargetPitch; + + m_aimYaw = targetYaw; + m_aimPitch = targetPitch; + + SetPoseParameter( AIRBOAT_GUN_YAW, m_aimYaw); + SetPoseParameter( AIRBOAT_GUN_PITCH, m_aimPitch ); + + InvalidateBoneCache(); + + // read back to avoid drift when hitting limits + // as long as the velocity is less than the delta between the limit and 180, this is fine. + m_aimPitch = GetPoseParameter( AIRBOAT_GUN_PITCH ); + m_aimYaw = GetPoseParameter( AIRBOAT_GUN_YAW ); +} +#pragma optimize("", on) + + +//----------------------------------------------------------------------------- +// Removes the ammo... +//----------------------------------------------------------------------------- +void CPropAirboat::RemoveAmmo( float flAmmoAmount ) +{ + m_flDrainRemainder += flAmmoAmount; + int nAmmoToRemove = (int)m_flDrainRemainder; + m_flDrainRemainder -= nAmmoToRemove; + m_nAmmoCount -= nAmmoToRemove; + if ( m_nAmmoCount < 0 ) + { + m_nAmmoCount = 0; + m_flDrainRemainder = 0.0f; + } +} + + +//----------------------------------------------------------------------------- +// Recharges the ammo... +//----------------------------------------------------------------------------- +void CPropAirboat::RechargeAmmo(void) +{ + if ( !m_bHasGun ) + { + m_nAmmoCount = -1; + return; + } + + int nMaxAmmo = sk_airboat_max_ammo.GetInt(); + if ( m_nAmmoCount == nMaxAmmo ) + return; + + float flRechargeRate = sk_airboat_recharge_rate.GetInt(); + float flChargeAmount = flRechargeRate * gpGlobals->frametime; + if ( m_flDrainRemainder != 0.0f ) + { + if ( m_flDrainRemainder >= flChargeAmount ) + { + m_flDrainRemainder -= flChargeAmount; + return; + } + else + { + flChargeAmount -= m_flDrainRemainder; + m_flDrainRemainder = 0.0f; + } + } + + m_flChargeRemainder += flChargeAmount; + int nAmmoToAdd = (int)m_flChargeRemainder; + m_flChargeRemainder -= nAmmoToAdd; + m_nAmmoCount += nAmmoToAdd; + if ( m_nAmmoCount > nMaxAmmo ) + { + m_nAmmoCount = nMaxAmmo; + m_flChargeRemainder = 0.0f; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::ComputeAimPoint( Vector *pVecAimPoint ) +{ + Vector vecEyeDirection; + + if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE ) + { + // Use autoaim as the eye dir. + autoaim_params_t params; + + params.m_fScale = AUTOAIM_SCALE_DEFAULT * sv_vehicle_autoaim_scale.GetFloat(); + params.m_fMaxDist = autoaim_max_dist.GetFloat(); + m_hPlayer->GetAutoaimVector( params ); + + vecEyeDirection = params.m_vecAutoAimDir; + } + else + { + m_hPlayer->EyeVectors( &vecEyeDirection, NULL, NULL ); + } + + Vector vecEndPos; + VectorMA( m_hPlayer->EyePosition(), MAX_TRACE_LENGTH, vecEyeDirection, vecEndPos ); + trace_t trace; + UTIL_TraceLine( m_hPlayer->EyePosition(), vecEndPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &trace ); + *pVecAimPoint = trace.endpos; +} + + +//----------------------------------------------------------------------------- +// Purpose: Manages animation and sound state. +//----------------------------------------------------------------------------- +void CPropAirboat::Think(void) +{ + BaseClass::Think(); + + // set handbrake after physics sim settles down +// if ( gpGlobals->curtime < m_flHandbrakeTime ) +// { +// SetNextThink( gpGlobals->curtime ); +// } +// else if ( !m_bInitialHandbrake ) // after initial timer expires, set the handbrake +// { +// m_bInitialHandbrake = true; +// m_VehiclePhysics.SetHandbrake( true ); +// m_VehiclePhysics.Think(); +// } + + // Find the vertical extents of the boat + Vector startPos, endPos; + CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 1.0f ), &startPos ); + CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 0.0f ), &endPos ); + + // Look for water along that volume. + // Make a very vertically thin box and sweep it along the ray. + Vector vecMins = CollisionProp()->OBBMins(); + Vector vecMaxs = CollisionProp()->OBBMaxs(); + vecMins.z = -0.1f; + vecMaxs.z = 0.1f; + + trace_t tr; + UTIL_TraceHull( startPos, endPos, vecMins, vecMaxs, (CONTENTS_WATER|CONTENTS_SLIME), this, COLLISION_GROUP_NONE, &tr ); + + // If we hit something, then save off the info + if ( tr.fraction != 1.0f ) + { + m_nExactWaterLevel = tr.endpos.z; + + // Classify what we're in + if ( tr.contents & CONTENTS_SLIME ) + { + // We fake this value to mean type, instead of level + SetWaterLevel( 2 ); + } + else + { + // This simply signifies water + SetWaterLevel( 1 ); + } + } + else + { + // Not in water + SetWaterLevel( 0 ); + } + + StudioFrameAdvance(); + + // If the enter or exit animation has finished, tell the server vehicle + if ( IsSequenceFinished() && ( m_bEnterAnimOn || m_bExitAnimOn ) ) + { + // The first few time we get into the jeep, print the jeep help + if ( m_iNumberOfEntries < hud_airboathint_numentries.GetInt() && !m_bExitAnimOn ) + { + UTIL_HudHintText( m_hPlayer, "#Valve_Hint_BoatKeys" ); + m_iNumberOfEntries++; + } + + GetServerVehicle()->HandleEntryExitFinish( m_bExitAnimOn, false ); + + // Start the vehicle's idle animation + ResetSequence(LookupSequence("propeller_spin1")); + ResetClientsideFrame(); + } + + // FIXME: Slam the crosshair every think -- if we don't do this it disappears randomly, never to return. + if ( ( m_hPlayer.Get() != NULL ) && !( m_bEnterAnimOn || m_bExitAnimOn ) ) + { + m_hPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_VEHICLE_CROSSHAIR; + } + + // Aim the gun + if ( HasGun() && m_hPlayer.Get() && !m_bEnterAnimOn && !m_bExitAnimOn ) + { + Vector vecAimPoint; + ComputeAimPoint( &vecAimPoint ); + AimGunAt( vecAimPoint, gpGlobals->frametime ); + } + + if ( ShouldForceExit() ) + { + ClearForcedExit(); + m_hPlayer->LeaveVehicle(); + } + + if ( HasGun() && ( m_nGunState == GUN_STATE_IDLE ) ) + { + RechargeAmmo(); + } + + UpdateSound(); + UpdatePropeller(); + UpdateGauge(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::UpdatePropeller() +{ + if ((m_bExitAnimOn) || (m_bEnterAnimOn)) + return; + + #define SPIN_RATE_MED 0.2 + #define SPIN_RATE_HIGH 0.6 + + // Determine target spin rate from throttle. + float flTargetSpinRate = m_flThrottle; + if ((flTargetSpinRate == 0) && (m_hPlayer)) + { + // Always keep the fan moving a little when we have a driver. + flTargetSpinRate = 0.2; + } + + // Save the current spin rate to determine state transitions. + float flPrevSpinRate = m_flSpinRate; + + // Determine new spin rate, + if (m_flSpinRate < flTargetSpinRate) + { + if (flTargetSpinRate > 0) + { + m_flSpinRate += gpGlobals->frametime * 1.0; + } + else + { + m_flSpinRate += gpGlobals->frametime * 0.4; + } + + if (m_flSpinRate > flTargetSpinRate) + { + m_flSpinRate = flTargetSpinRate; + } + } + else if (m_flSpinRate > flTargetSpinRate) + { + m_flSpinRate -= gpGlobals->frametime * 0.4; + if (m_flSpinRate < flTargetSpinRate) + { + m_flSpinRate = flTargetSpinRate; + } + } + + // Update prop & blur based on new spin rate. + if (fabs(m_flSpinRate) > SPIN_RATE_HIGH) + { + if (fabs(flPrevSpinRate) <= SPIN_RATE_HIGH) + { + SetBodygroup(AIRBOAT_BODYGROUP_PROP, false); + SetBodygroup(AIRBOAT_BODYGROUP_BLUR, true); + SetSequence(LookupSequence("propeller_spin1")); + } + } + else if (fabs(m_flSpinRate) > SPIN_RATE_MED) + { + if ((fabs(flPrevSpinRate) <= SPIN_RATE_MED) || (fabs(flPrevSpinRate) > SPIN_RATE_HIGH)) + { + SetBodygroup(AIRBOAT_BODYGROUP_PROP, true); + SetBodygroup(AIRBOAT_BODYGROUP_BLUR, true); + SetSequence(LookupSequence("propeller_spin1")); + } + } + else + { + if (fabs(flPrevSpinRate) > SPIN_RATE_MED) + { + SetBodygroup(AIRBOAT_BODYGROUP_PROP, true); + SetBodygroup(AIRBOAT_BODYGROUP_BLUR, false); + SetSequence(LookupSequence("propeller_spin1")); + } + } + + SetPlaybackRate( m_flSpinRate ); + + m_flPrevThrottle = m_flThrottle; +} + + +//----------------------------------------------------------------------------- +// Purpose: Updates the speedometer. +//----------------------------------------------------------------------------- +void CPropAirboat::UpdateGauge() +{ + CFourWheelVehiclePhysics *pPhysics = GetPhysics(); + int speed = pPhysics->GetSpeed(); + int maxSpeed = pPhysics->GetMaxSpeed(); + float speedRatio = clamp( (float)speed / (float)maxSpeed, 0, 1 ); + + SetPoseParameter( "Gauge", speedRatio ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::CreateSounds() +{ + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + + CPASAttenuationFilter filter( this ); + + if (!m_pEngineSound) + { + m_pEngineSound = controller.SoundCreate( filter, entindex(), "Airboat_engine_idle" ); + controller.Play( m_pEngineSound, 0, 100 ); + } + + if (!m_pFanSound) + { + m_pFanSound = controller.SoundCreate( filter, entindex(), "Airboat_fan_idle" ); + controller.Play( m_pFanSound, 0, 100 ); + } + + if (!m_pFanMaxSpeedSound) + { + m_pFanMaxSpeedSound = controller.SoundCreate( filter, entindex(), "Airboat_fan_fullthrottle" ); + controller.Play( m_pFanMaxSpeedSound, 0, 100 ); + } + + if (!m_pWaterStoppedSound) + { + m_pWaterStoppedSound = controller.SoundCreate( filter, entindex(), "Airboat_water_stopped" ); + controller.Play( m_pWaterStoppedSound, 0, 100 ); + } + + if (!m_pWaterFastSound) + { + m_pWaterFastSound = controller.SoundCreate( filter, entindex(), "Airboat_water_fast" ); + controller.Play( m_pWaterFastSound, 0, 100 ); + } + + if (!m_pGunFiringSound) + { + m_pGunFiringSound = controller.SoundCreate( filter, entindex(), "Airboat.FireGunLoop" ); + controller.Play( m_pGunFiringSound, 0, 100 ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::StopLoopingSounds() +{ + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + + controller.SoundDestroy( m_pEngineSound ); + m_pEngineSound = NULL; + + controller.SoundDestroy( m_pFanSound ); + m_pFanSound = NULL; + + controller.SoundDestroy( m_pFanMaxSpeedSound ); + m_pFanMaxSpeedSound = NULL; + + controller.SoundDestroy( m_pWaterStoppedSound ); + m_pWaterStoppedSound = NULL; + + controller.SoundDestroy( m_pWaterFastSound ); + m_pWaterFastSound = NULL; + + controller.SoundDestroy( m_pGunFiringSound ); + m_pGunFiringSound = NULL; + + BaseClass::StopLoopingSounds(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Manage the state of the engine sound. +//----------------------------------------------------------------------------- +void CPropAirboat::UpdateEngineSound( CSoundEnvelopeController &controller, float speedRatio ) +{ + #define ENGINE_MIN_VOLUME 0.22 + #define ENGINE_MAX_VOLUME 0.62 + #define ENGINE_MIN_PITCH 80 + #define ENGINE_MAX_PITCH 140 + #define ENGINE_DUCK_TIME 4.0 + + if ( controller.SoundGetVolume(m_pEngineSound ) == 0 ) + { + if ( gpGlobals->curtime > m_flEngineIdleTime ) + { + // If we've finished playing the engine start sound, start playing the idle sound. + controller.Play( m_pEngineSound, ENGINE_MAX_VOLUME, 100 ); + + // Ramp down the engine idle sound over time so that we can ramp it back up again based on speed. + controller.SoundChangeVolume( m_pEngineSound, ENGINE_MIN_VOLUME, ENGINE_DUCK_TIME ); + controller.SoundChangePitch( m_pEngineSound, ENGINE_MIN_PITCH, ENGINE_DUCK_TIME ); + + // Reduce the volume of the engine idle sound after our ears get 'used' to it. + m_flEngineDuckTime = gpGlobals->curtime + ENGINE_DUCK_TIME; + } + } + else if ( gpGlobals->curtime > m_flEngineDuckTime ) + { + controller.SoundChangeVolume( m_pEngineSound, RemapValClamped(speedRatio, 0, 1.0, ENGINE_MIN_VOLUME, ENGINE_MAX_VOLUME ), 0.0 ); + controller.SoundChangePitch( m_pEngineSound, RemapValClamped( speedRatio, 0, 1.0, ENGINE_MIN_PITCH, ENGINE_MAX_PITCH ), 0 ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::UpdateFanSound( CSoundEnvelopeController &controller, float speedRatio ) +{ + #define FAN_MIN_VOLUME 0.0 + #define FAN_MAX_VOLUME 0.82 + #define FAN_DUCK_VOLUME 0.22 + #define FAN_CHANGE_VOLUME_TIME 1.0 // seconds over which to change the volume + #define FAN_DUCK_TIME 2.0 // seconds over which to duck the fan sound + + // Manage the state of the fan sound. + if (speedRatio >= 0.8) + { + // Crossfade between a 'max speed' fan sound and the normal fan sound. + controller.SoundChangeVolume( m_pFanSound, RemapValClamped( speedRatio, 0.8, 1.0, FAN_MAX_VOLUME, FAN_MIN_VOLUME ), FAN_CHANGE_VOLUME_TIME ); + controller.SoundChangeVolume( m_pFanMaxSpeedSound, RemapValClamped( speedRatio, 0.8, 1.0, FAN_MIN_VOLUME, FAN_MAX_VOLUME ), FAN_CHANGE_VOLUME_TIME ); + + if (!m_bFadeOutFan) + { + m_bFadeOutFan = true; + controller.SoundChangeVolume( m_pFanSound, FAN_DUCK_VOLUME, FAN_DUCK_TIME ); + } + } + else + { + m_bFadeOutFan = false; + controller.SoundChangeVolume( m_pFanSound, RemapValClamped( fabs(m_flThrottle), 0, 1.0, FAN_MIN_VOLUME, FAN_MAX_VOLUME ), 0.25 ); + controller.SoundChangeVolume( m_pFanMaxSpeedSound, 0.0, 0.0 ); + } + + controller.SoundChangePitch( m_pFanSound, 100 * (fabs(m_flThrottle) + 0.2), 0.25 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::UpdateWaterSound( CSoundEnvelopeController &controller, float speedRatio ) +{ + int nWaterLevel = GetWaterLevel(); + + // Manage the state of the water stopped sound (gentle lapping at the pontoons). + if ( nWaterLevel == 0 ) + { + controller.SoundChangeVolume(m_pWaterStoppedSound, 0.0, 0.0); + } + else + { + if ( m_nPrevWaterLevel == 0 ) + { + Vector vecVelocityWorld; + GetVelocity( &vecVelocityWorld, NULL ); + + if ( ( fabs( vecVelocityWorld.x ) > 400 ) || ( fabs( vecVelocityWorld.y ) > 400 ) || ( fabs( vecVelocityWorld.z ) > 400 ) ) + { + // Landed in the water. Play a splash sound. + EmitSound( "Airboat_impact_splash" ); + + if ( fabs( vecVelocityWorld.z ) > 200 ) + { + // Landed hard in the water. Play a smack sound. + EmitSound( "Airboat_impact_hard" ); + } + } + } + + if (speedRatio <= 0.1) + { + if (!controller.SoundGetVolume(m_pWaterStoppedSound)) + { + // Fade in the water stopped sound over 2 seconds. + controller.SoundChangeVolume(m_pWaterStoppedSound, 1.0, 2.0); + m_flWaterStoppedPitchTime = gpGlobals->curtime + random->RandomFloat(1.0, 3.0); + } + else if (gpGlobals->curtime > m_flWaterStoppedPitchTime) + { + controller.SoundChangeVolume(m_pWaterStoppedSound, random->RandomFloat(0.2, 1.0), random->RandomFloat(1.0, 3.0)); + controller.SoundChangePitch(m_pWaterStoppedSound, random->RandomFloat(90, 110), random->RandomFloat(1.0, 3.0)); + m_flWaterStoppedPitchTime = gpGlobals->curtime + random->RandomFloat(2.0, 4.0); + } + } + else + { + if (controller.SoundGetVolume(m_pWaterStoppedSound)) + { + // Fade out the water stopped sound over 1 second. + controller.SoundChangeVolume(m_pWaterStoppedSound, 0.0, 1.0); + } + } + } + + // Manage the state of the water fast sound (water hissing under the pontoons). + if ( nWaterLevel == 0 ) + { + controller.SoundChangeVolume(m_pWaterFastSound, 0.0, 0.0); + } + else + { + controller.SoundChangeVolume( m_pWaterFastSound, speedRatio, 0.0 ); + } + + m_nPrevWaterLevel = nWaterLevel; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::UpdateSound() +{ + if (!GetDriver()) + return; + + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + + // Sample the data that we need for sounds. + CFourWheelVehiclePhysics *pPhysics = GetPhysics(); + int speed = pPhysics->GetSpeed(); + int maxSpeed = pPhysics->GetMaxSpeed(); + float speedRatio = clamp((float)speed / (float)maxSpeed, 0, 1); + + //Msg("speedRatio=%f\n", speedRatio); + + UpdateWeaponSound(); + UpdateEngineSound( controller, speedRatio ); + UpdateFanSound( controller, speedRatio ); + UpdateWaterSound( controller, speedRatio ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::UpdateSplashEffects( void ) +{ + // Splash effects. + CreateSplash( AIRBOAT_SPLASH_RIPPLE ); +// CreateSplash( AIRBOAT_SPLASH_SPRAY ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CPropAirboat::GetTracerType( void ) +{ + if ( gpGlobals->curtime >= m_flNextHeavyShotTime ) + return "AirboatGunHeavyTracer"; + + return "AirboatGunTracer"; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::DoMuzzleFlash( void ) +{ + CEffectData data; + data.m_nEntIndex = entindex(); + data.m_nAttachmentIndex = m_nGunBarrelAttachment; + data.m_flScale = 1.0f; + DispatchEffect( "AirboatMuzzleFlash", data ); + + BaseClass::DoMuzzleFlash(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#define GUN_WINDUP_TIME 1.5f + +// NVNT Convar for airboat gun magnitude +ConVar hap_airboat_gun_mag("hap_airboat_gun_mag", "3", 0); + +void CPropAirboat::FireGun( ) +{ + // Get the gun position. + Vector vecGunPosition; + Vector vecForward; + GetAttachment( m_nGunBarrelAttachment, vecGunPosition, &vecForward ); + + // NOTE: For the airboat, unable to fire really means the aim is clamped + Vector vecAimPoint; + if ( !m_bUnableToFire ) + { + // Trace from eyes and see what we hit. + ComputeAimPoint( &vecAimPoint ); + } + else + { + // We hit the clamp; just fire whichever way the gun is facing + VectorMA( vecGunPosition, 1000.0f, vecForward, vecAimPoint ); + } + + // Get a ray from the gun to the target. + Vector vecRay = vecAimPoint - vecGunPosition; + VectorNormalize( vecRay ); + + /* + // Get the aiming direction + Vector vecRay; + AngleVectors( vecGunAngles, &vecRay ); + VectorNormalize( vecRay ); + */ + + CAmmoDef *pAmmoDef = GetAmmoDef(); + int ammoType = pAmmoDef->Index( "AirboatGun" ); + +#if defined( WIN32 ) && !defined( _X360 ) + // NVNT punch the players haptics by the magnitude cvar each round fired + HapticPunch(m_hPlayer,0,0,hap_airboat_gun_mag.GetFloat()); +#endif + + FireBulletsInfo_t info; + info.m_vecSrc = vecGunPosition; + info.m_vecDirShooting = vecRay; + info.m_flDistance = 4096; + info.m_iAmmoType = ammoType; + info.m_nFlags = FIRE_BULLETS_TEMPORARY_DANGER_SOUND; + + if ( gpGlobals->curtime >= m_flNextHeavyShotTime ) + { + info.m_iShots = 1; + info.m_vecSpread = VECTOR_CONE_PRECALCULATED; + info.m_flDamageForceScale = 1000.0f; + } + else + { + info.m_iShots = 2; + info.m_vecSpread = VECTOR_CONE_5DEGREES; + } + + FireBullets( info ); + + CBaseEntity *pDriver = GetDriver(); + CBasePlayer *pPlayerDriver; + if( pDriver && pDriver->IsPlayer() ) + { + pPlayerDriver = dynamic_cast<CBasePlayer*>(pDriver); + if( pPlayerDriver ) + { + pPlayerDriver->RumbleEffect( RUMBLE_AIRBOAT_GUN, 0, RUMBLE_FLAG_LOOP|RUMBLE_FLAG_ONLYONE ); + } + } + + DoMuzzleFlash(); + + // NOTE: This must occur after FireBullets + if ( gpGlobals->curtime >= m_flNextHeavyShotTime ) + { + m_flNextHeavyShotTime = gpGlobals->curtime + CANNON_HEAVY_SHOT_INTERVAL; + } + + if ( gpGlobals->curtime >= m_flNextGunShakeTime ) + { + UTIL_ScreenShakeObject( this, WorldSpaceCenter(), 0.2, 250.0, CANNON_SHAKE_INTERVAL, 250, SHAKE_START ); + m_flNextGunShakeTime = gpGlobals->curtime + 0.5 * CANNON_SHAKE_INTERVAL; + } + + // Specifically kill APC missiles in the cone. But we're going to totally cheat + // because it's hard to hit them when they are close. + // Use the player's eye position as the center of the cone. + if ( !m_hPlayer ) + return; + + Vector vecEyeDirection, vecEyePosition; + if ( !m_bUnableToFire ) + { + if ( IsX360() ) + { + GetAttachment( m_nGunBarrelAttachment, vecEyePosition, &vecEyeDirection ); + } + else + { + vecEyePosition = m_hPlayer->EyePosition(); + m_hPlayer->EyeVectors( &vecEyeDirection, NULL, NULL ); + } + } + else + { + vecEyePosition = vecGunPosition; + vecEyeDirection = vecRay; + } + + CAPCMissile *pEnt = FindAPCMissileInCone( vecEyePosition, vecEyeDirection, 2.5f ); + if ( pEnt && (pEnt->GetHealth() > 0) ) + { + CTakeDamageInfo info( this, this, 1, DMG_AIRBOAT ); + CalculateBulletDamageForce( &info, ammoType, vecRay, pEnt->WorldSpaceCenter() ); + pEnt->TakeDamage( info ); + + Vector vecVelocity = pEnt->GetAbsVelocity(); + + // Pick a vector perpendicular to the vecRay which will push it away from the airboat + Vector vecPerp; + CrossProduct( Vector( 0, 0, 1 ), vecRay, vecPerp ); + vecPerp.z = 0.0f; + if ( VectorNormalize( vecPerp ) > 1e-3 ) + { + Vector vecCurrentDir; + GetVectors( &vecCurrentDir, NULL, NULL ); + if ( DotProduct( vecPerp, vecCurrentDir ) > 0.0f ) + { + vecPerp *= -1.0f; + } + + vecPerp *= random->RandomFloat( 15, 25 ); + vecVelocity += vecPerp; + pEnt->SetAbsVelocity( vecVelocity ); +// pEnt->DisableGuiding(); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#define FIRING_DISCHARGE_RATE (1.0f / 3.0f) + +void CPropAirboat::UpdateGunState( CUserCmd *ucmd ) +{ + bool bStopRumble = false; + + if ( ucmd->buttons & IN_ATTACK ) + { + if ( m_nGunState == GUN_STATE_IDLE ) + { +// AddGestureSequence( LookupSequence( "fire_gun" ) ); + m_nGunState = GUN_STATE_FIRING; + } + + if ( m_nAmmoCount > 0 ) + { + RemoveAmmo( FIRING_DISCHARGE_RATE ); + FireGun( ); + + if ( m_nAmmoCount == 0 ) + { + EmitSound( "Airboat.FireGunRevDown" ); + bStopRumble = true; +// RemoveAllGestures(); + } + } + } + else + { + if ( m_nGunState != GUN_STATE_IDLE ) + { + if ( m_nAmmoCount != 0 ) + { + EmitSound( "Airboat.FireGunRevDown" ); + bStopRumble = true; +// RemoveAllGestures(); + } + m_nGunState = GUN_STATE_IDLE; + } + } + + if( bStopRumble ) + { + CBaseEntity *pDriver = GetDriver(); + CBasePlayer *pPlayerDriver; + if( pDriver && pDriver->IsPlayer() ) + { + pPlayerDriver = dynamic_cast<CBasePlayer*>(pDriver); + if( pPlayerDriver ) + { + pPlayerDriver->RumbleEffect( RUMBLE_AIRBOAT_GUN, 0, RUMBLE_FLAG_STOP ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::DriveVehicle( float flFrameTime, CUserCmd *ucmd, int iButtonsDown, int iButtonsReleased ) +{ + if ( ucmd->impulse == 100 ) + { + if (HeadlightIsOn()) + { + HeadlightTurnOff(); + } + else + { + HeadlightTurnOn(); + } + } + + // Fire gun. + if ( HasGun() ) + { + UpdateGunState( ucmd ); + } + + m_VehiclePhysics.UpdateDriverControls( ucmd, TICK_INTERVAL ); + + // Create splashes. + UpdateSplashEffects(); + + // Save this data. + m_flThrottle = m_VehiclePhysics.GetThrottle(); + m_nSpeed = m_VehiclePhysics.GetSpeed(); + m_nRPM = m_VehiclePhysics.GetRPM(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPlayer - +// *pMoveData - +//----------------------------------------------------------------------------- +void CPropAirboat::ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMoveData ) +{ + BaseClass::ProcessMovement( pPlayer, pMoveData ); + + if ( gpGlobals->frametime != 0 ) + { + // Create danger sounds in front of the vehicle. + CreateDangerSounds(); + + // Play a sound around us to make NPCs pay attention to us + if ( m_VehiclePhysics.GetThrottle() > 0 ) + { + CSoundEnt::InsertSound( SOUND_PLAYER_VEHICLE, pPlayer->GetAbsOrigin(), 3500, 0.1f, pPlayer, SOUNDENT_CHANNEL_REPEATED_PHYSICS_DANGER ); + } + } + + Vector vecVelocityWorld; + GetVelocity( &vecVelocityWorld, NULL ); + Vector vecVelocityLocal; + WorldToEntitySpace( GetAbsOrigin() + vecVelocityWorld, &vecVelocityLocal ); + + m_vecPhysVelocity = vecVelocityLocal; +} + + +//----------------------------------------------------------------------------- +// Purpose: Create danger sounds in front of the vehicle. +//----------------------------------------------------------------------------- +void CPropAirboat::CreateDangerSounds( void ) +{ + QAngle vehicleAngles = GetLocalAngles(); + Vector vecStart = GetAbsOrigin(); + Vector vecDir, vecRight; + + GetVectors( &vecDir, &vecRight, NULL ); + + const float soundDuration = 0.25; + float speed = m_VehiclePhysics.GetHLSpeed(); + + // Make danger sounds ahead of the vehicle + if ( fabs(speed) > 120 ) + { + Vector vecSpot; + + float steering = m_VehiclePhysics.GetSteering(); + if ( steering != 0 ) + { + if ( speed > 0 ) + { + vecDir += vecRight * steering * 0.5; + } + else + { + vecDir -= vecRight * steering * 0.5; + } + VectorNormalize(vecDir); + } + + const float radius = speed * 0.4; + + // 0.7 seconds ahead + vecSpot = vecStart + vecDir * (speed * 0.7f); + CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, radius, soundDuration, this, SOUNDENT_CHANNEL_REPEATED_DANGER ); + CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, vecSpot, radius, soundDuration, this, SOUNDENT_CHANNEL_REPEATED_PHYSICS_DANGER ); + //NDebugOverlay::Box(vecSpot, Vector(-radius,-radius,-radius),Vector(radius,radius,radius), 255, 0, 255, 0, soundDuration); + +#if 0 + // put sounds a bit to left and right but slightly closer to vehicle to make + // a "cone" of sound in front of it. + trace_t tr; + vecSpot = vecStart + vecDir * (speed * 0.5f) - vecRight * speed * 0.5; + UTIL_TraceLine( vecStart, vecSpot, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, 400, soundDuration, this, 1 ); + + vecSpot = vecStart + vecDir * (speed * 0.5f) + vecRight * speed * 0.5; + UTIL_TraceLine( vecStart, vecSpot, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, 400, soundDuration, this, 2); +#endif + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::DampenEyePosition( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles ) +{ + // Get the frametime. (Check to see if enough time has passed to warrent dampening). + float flFrameTime = gpGlobals->frametime; + if ( flFrameTime < AIRBOAT_FRAMETIME_MIN ) + { + vecVehicleEyePos = m_vecLastEyePos; + DampenUpMotion( vecVehicleEyePos, vecVehicleEyeAngles, 0.0f ); + return; + } + + // Keep static the sideways motion. + + // Dampen forward/backward motion. + DampenForwardMotion( vecVehicleEyePos, vecVehicleEyeAngles, flFrameTime ); + + // Blend up/down motion. + DampenUpMotion( vecVehicleEyePos, vecVehicleEyeAngles, flFrameTime ); +} + + +//----------------------------------------------------------------------------- +// Use the controller as follows: +// speed += ( pCoefficientsOut[0] * ( targetPos - currentPos ) + pCoefficientsOut[1] * ( targetSpeed - currentSpeed ) ) * flDeltaTime; +//----------------------------------------------------------------------------- +void CPropAirboat::ComputePDControllerCoefficients( float *pCoefficientsOut, + float flFrequency, float flDampening, + float flDeltaTime ) +{ + float flKs = 9.0f * flFrequency * flFrequency; + float flKd = 4.5f * flFrequency * flDampening; + + float flScale = 1.0f / ( 1.0f + flKd * flDeltaTime + flKs * flDeltaTime * flDeltaTime ); + + pCoefficientsOut[0] = flKs * flScale; + pCoefficientsOut[1] = ( flKd + flKs * flDeltaTime ) * flScale; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::DampenForwardMotion( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles, float flFrameTime ) +{ + // Get forward vector. + Vector vecForward; + AngleVectors( vecVehicleEyeAngles, &vecForward); + + // Simulate the eye position forward based on the data from last frame + // (assumes no acceleration - it will get that from the "spring"). + Vector vecCurrentEyePos = m_vecLastEyePos + m_vecEyeSpeed * flFrameTime; + + // Calculate target speed based on the current vehicle eye position and the last vehicle eye position and frametime. + Vector vecVehicleEyeSpeed = ( vecVehicleEyePos - m_vecLastEyeTarget ) / flFrameTime; + m_vecLastEyeTarget = vecVehicleEyePos; + + // Calculate the speed and position deltas. + Vector vecDeltaSpeed = vecVehicleEyeSpeed - m_vecEyeSpeed; + Vector vecDeltaPos = vecVehicleEyePos - vecCurrentEyePos; + + // Clamp. + if ( vecDeltaPos.Length() > AIRBOAT_DELTA_LENGTH_MAX ) + { + float flSign = vecForward.Dot( vecVehicleEyeSpeed ) >= 0.0f ? -1.0f : 1.0f; + vecVehicleEyePos += flSign * ( vecForward * AIRBOAT_DELTA_LENGTH_MAX ); + m_vecLastEyePos = vecVehicleEyePos; + m_vecEyeSpeed = vecVehicleEyeSpeed; + return; + } + + // Generate an updated (dampening) speed for use in next frames position extrapolation. + float flCoefficients[2]; + ComputePDControllerCoefficients( flCoefficients, r_AirboatViewDampenFreq.GetFloat(), r_AirboatViewDampenDamp.GetFloat(), flFrameTime ); + m_vecEyeSpeed += ( ( flCoefficients[0] * vecDeltaPos + flCoefficients[1] * vecDeltaSpeed ) * flFrameTime ); + + // Save off data for next frame. + m_vecLastEyePos = vecCurrentEyePos; + + // Move eye forward/backward. + Vector vecForwardOffset = vecForward * ( vecForward.Dot( vecDeltaPos ) ); + vecVehicleEyePos -= vecForwardOffset; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::DampenUpMotion( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles, float flFrameTime ) +{ + // Get up vector. + Vector vecUp; + AngleVectors( vecVehicleEyeAngles, NULL, NULL, &vecUp ); + vecUp.z = clamp( vecUp.z, 0.0f, vecUp.z ); + vecVehicleEyePos.z += r_AirboatViewZHeight.GetFloat() * vecUp.z; + + // NOTE: Should probably use some damped equation here. +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropAirboat::CreateSplash( int nSplashType ) +{ + if ( GetWaterLevel( ) == 0 ) + return; + + Vector vecSplashPoint; + Vector vecForward, vecUp; + GetAttachment( m_nSplashAttachment, vecSplashPoint, &vecForward, &vecUp, NULL ); + + CEffectData data; + data.m_fFlags = 0; + data.m_vOrigin = vecSplashPoint; + if ( GetWaterType() & CONTENTS_SLIME ) + { + data.m_fFlags |= FX_WATER_IN_SLIME; + } + + switch ( nSplashType ) + { + case AIRBOAT_SPLASH_SPRAY: + { + Vector vecSplashDir; + vecSplashDir = ( vecForward + vecUp ) * 0.5f; + VectorNormalize( vecSplashDir ); + data.m_vNormal = vecSplashDir; + data.m_flScale = 10.0f + random->RandomFloat( 0, 10.0f * 0.25 ); + //DispatchEffect( "waterripple", data ); + DispatchEffect( "watersplash", data ); + } + case AIRBOAT_SPLASH_RIPPLE: + { + /* + Vector vecSplashDir; + vecSplashDir = vecUp; + data.m_vNormal = vecSplashDir; + data.m_flScale = AIRBOAT_SPLASH_RIPPLE_SIZE + random->RandomFloat( 0, AIRBOAT_SPLASH_RIPPLE_SIZE * 0.25 ); + DispatchEffect( "waterripple", data ); + */ + } + default: + { + return; + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Overloaded to calculate stress damage. +//----------------------------------------------------------------------------- +void CPropAirboat::VPhysicsUpdate( IPhysicsObject *pPhysics ) +{ + BaseClass::VPhysicsUpdate( pPhysics ); + + if ( airboat_fatal_stress.GetFloat() > 0 ) + { + ApplyStressDamage( pPhysics ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns the damage that should be dealt to the driver due to +// stress (vphysics objects exerting pressure on us). +//----------------------------------------------------------------------------- +float CPropAirboat::CalculatePhysicsStressDamage( vphysics_objectstress_t *pStressOut, IPhysicsObject *pPhysics ) +{ + vphysics_objectstress_t stressOut; + CalculateObjectStress( pPhysics, this, &stressOut ); + + //if ( ( stressOut.exertedStress > 100 ) || ( stressOut.receivedStress > 100 ) ) + // Msg( "stress: %f %d %f\n", stressOut.exertedStress, stressOut.hasNonStaticStress, stressOut.receivedStress ); + + // Make sure the stress isn't from being stuck inside some static object. + // If we're being crushed by more than the fatal stress amount, kill the driver. + if ( stressOut.hasNonStaticStress && ( stressOut.receivedStress > airboat_fatal_stress.GetFloat() ) ) + { + // if stuck, don't do this! + if ( !(pPhysics->GetGameFlags() & FVPHYSICS_PENETRATING) ) + { + // Kill the driver! + return 1000; + } + } + + return 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Applies stress damage to the player/driver. +//----------------------------------------------------------------------------- +void CPropAirboat::ApplyStressDamage( IPhysicsObject *pPhysics ) +{ + vphysics_objectstress_t stressOut; + float damage = CalculatePhysicsStressDamage( &stressOut, pPhysics ); + if ( ( damage > 0 ) && ( m_hPlayer != NULL ) ) + { + CTakeDamageInfo dmgInfo( GetWorldEntity(), GetWorldEntity(), vec3_origin, vec3_origin, damage, DMG_CRUSH ); + dmgInfo.SetDamageForce( Vector( 0, 0, -stressOut.receivedStress * GetCurrentGravity() * gpGlobals->frametime ) ); + dmgInfo.SetDamagePosition( GetAbsOrigin() ); + m_hPlayer->TakeDamage( dmgInfo ); + } +} + |