You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
931 lines
29 KiB
931 lines
29 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Client side implementation of the airboat. |
|
// |
|
// - Dampens motion of driver's view to reduce nausea. |
|
// - Autocenters driver's view after a period of inactivity. |
|
// - Controls headlights. |
|
// - Controls curve parameters for pitch/roll blending. |
|
// |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "c_prop_vehicle.h" |
|
#include "datacache/imdlcache.h" |
|
#include "flashlighteffect.h" |
|
#include "movevars_shared.h" |
|
#include "ammodef.h" |
|
#include "SpriteTrail.h" |
|
#include "beamdraw.h" |
|
#include "enginesprite.h" |
|
#include "fx_quad.h" |
|
#include "fx.h" |
|
#include "fx_water.h" |
|
#include "engine/ivdebugoverlay.h" |
|
#include "view.h" |
|
#include "clienteffectprecachesystem.h" |
|
#include "c_basehlplayer.h" |
|
#include "vgui_controls/Controls.h" |
|
#include "vgui/ISurface.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
ConVar r_AirboatViewBlendTo( "r_AirboatViewBlendTo", "1", FCVAR_CHEAT ); |
|
ConVar r_AirboatViewBlendToScale( "r_AirboatViewBlendToScale", "0.03", FCVAR_CHEAT ); |
|
ConVar r_AirboatViewBlendToTime( "r_AirboatViewBlendToTime", "1.5", FCVAR_CHEAT ); |
|
|
|
ConVar cl_draw_airboat_wake( "cl_draw_airboat_wake", "1", FCVAR_CHEAT ); |
|
|
|
// Curve parameters for pitch/roll blending. |
|
// NOTE: Must restart (or create a new airboat) after changing these cvars! |
|
ConVar r_AirboatRollCurveZero( "r_AirboatRollCurveZero", "90.0", FCVAR_CHEAT ); // Roll less than this is clamped to zero. |
|
ConVar r_AirboatRollCurveLinear( "r_AirboatRollCurveLinear", "120.0", FCVAR_CHEAT ); // Roll greater than this is mapped directly. |
|
// Spline in between. |
|
|
|
ConVar r_AirboatPitchCurveZero( "r_AirboatPitchCurveZero", "25.0", FCVAR_CHEAT ); // Pitch less than this is clamped to zero. |
|
ConVar r_AirboatPitchCurveLinear( "r_AirboatPitchCurveLinear", "60.0", FCVAR_CHEAT ); // Pitch greater than this is mapped directly. |
|
// Spline in between. |
|
|
|
ConVar airboat_joy_response_move( "airboat_joy_response_move", "1" ); // Quadratic steering response |
|
|
|
|
|
#define AIRBOAT_DELTA_LENGTH_MAX 12.0f // 1 foot |
|
#define AIRBOAT_FRAMETIME_MIN 1e-6 |
|
|
|
#define HEADLIGHT_DISTANCE 1000 |
|
|
|
#define MAX_WAKE_POINTS 16 |
|
#define WAKE_POINT_MASK (MAX_WAKE_POINTS-1) |
|
|
|
#define WAKE_LIFETIME 0.5f |
|
|
|
//============================================================================= |
|
// |
|
// Client-side Airboat Class |
|
// |
|
class C_PropAirboat : public C_PropVehicleDriveable |
|
{ |
|
DECLARE_CLASS( C_PropAirboat, C_PropVehicleDriveable ); |
|
|
|
public: |
|
|
|
DECLARE_CLIENTCLASS(); |
|
DECLARE_INTERPOLATION(); |
|
DECLARE_DATADESC(); |
|
|
|
C_PropAirboat(); |
|
~C_PropAirboat(); |
|
|
|
public: |
|
|
|
// C_BaseEntity |
|
virtual void Simulate(); |
|
|
|
// IClientVehicle |
|
virtual void UpdateViewAngles( C_BasePlayer *pLocalPlayer, CUserCmd *pCmd ); |
|
virtual void OnEnteredVehicle( C_BasePlayer *pPlayer ); |
|
virtual int GetPrimaryAmmoType() const; |
|
virtual int GetPrimaryAmmoClip() const; |
|
virtual bool PrimaryAmmoUsesClips() const; |
|
virtual int GetPrimaryAmmoCount() const; |
|
virtual int GetJoystickResponseCurve() const; |
|
|
|
int DrawModel( int flags ); |
|
|
|
// Draws crosshair in the forward direction of the boat |
|
void DrawHudElements( ); |
|
|
|
private: |
|
|
|
void DrawPropWake( Vector origin, float speed ); |
|
void DrawPontoonSplash( Vector position, Vector direction, float speed ); |
|
void DrawPontoonWake( Vector startPos, Vector wakeDir, float wakeLength, float speed); |
|
|
|
void DampenEyePosition( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles ); |
|
void DampenForwardMotion( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles, float flFrameTime ); |
|
void DampenUpMotion( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles, float flFrameTime ); |
|
void ComputePDControllerCoefficients( float *pCoefficientsOut, float flFrequency, float flDampening, float flDeltaTime ); |
|
|
|
void UpdateHeadlight( void ); |
|
void UpdateWake( void ); |
|
int DrawWake( void ); |
|
void DrawSegment( const BeamSeg_t &beamSeg, const Vector &vNormal ); |
|
|
|
TrailPoint_t *GetTrailPoint( int n ) |
|
{ |
|
int nIndex = (n + m_nFirstStep) & WAKE_POINT_MASK; |
|
return &m_vecSteps[nIndex]; |
|
} |
|
|
|
private: |
|
|
|
Vector m_vecLastEyePos; |
|
Vector m_vecLastEyeTarget; |
|
Vector m_vecEyeSpeed; |
|
Vector m_vecTargetSpeed; |
|
|
|
float m_flViewAngleDeltaTime; |
|
|
|
bool m_bHeadlightIsOn; |
|
int m_nAmmoCount; |
|
CHeadlightEffect *m_pHeadlight; |
|
|
|
int m_nExactWaterLevel; |
|
|
|
TrailPoint_t m_vecSteps[MAX_WAKE_POINTS]; |
|
int m_nFirstStep; |
|
int m_nStepCount; |
|
float m_flUpdateTime; |
|
|
|
TimedEvent m_SplashTime; |
|
CMeshBuilder m_Mesh; |
|
|
|
Vector m_vecPhysVelocity; |
|
}; |
|
|
|
IMPLEMENT_CLIENTCLASS_DT( C_PropAirboat, DT_PropAirboat, CPropAirboat ) |
|
RecvPropBool( RECVINFO( m_bHeadlightIsOn ) ), |
|
RecvPropInt( RECVINFO( m_nAmmoCount ) ), |
|
RecvPropInt( RECVINFO( m_nExactWaterLevel ) ), |
|
RecvPropInt( RECVINFO( m_nWaterLevel ) ), |
|
RecvPropVector( RECVINFO( m_vecPhysVelocity ) ), |
|
END_RECV_TABLE() |
|
|
|
|
|
BEGIN_DATADESC( C_PropAirboat ) |
|
DEFINE_FIELD( m_vecLastEyePos, FIELD_POSITION_VECTOR ), |
|
DEFINE_FIELD( m_vecLastEyeTarget, FIELD_POSITION_VECTOR ), |
|
DEFINE_FIELD( m_vecEyeSpeed, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_vecTargetSpeed, FIELD_VECTOR ), |
|
//DEFINE_FIELD( m_flViewAngleDeltaTime, FIELD_FLOAT ), |
|
END_DATADESC() |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Constructor |
|
//----------------------------------------------------------------------------- |
|
C_PropAirboat::C_PropAirboat() |
|
{ |
|
m_vecEyeSpeed.Init(); |
|
m_flViewAngleDeltaTime = 0.0f; |
|
m_pHeadlight = NULL; |
|
|
|
m_ViewSmoothingData.flPitchCurveZero = r_AirboatPitchCurveZero.GetFloat(); |
|
m_ViewSmoothingData.flPitchCurveLinear = r_AirboatPitchCurveLinear.GetFloat(); |
|
m_ViewSmoothingData.flRollCurveZero = r_AirboatRollCurveZero.GetFloat(); |
|
m_ViewSmoothingData.flRollCurveLinear = r_AirboatRollCurveLinear.GetFloat(); |
|
|
|
m_ViewSmoothingData.rollLockData.flLockInterval = 1.5; |
|
m_ViewSmoothingData.rollLockData.flUnlockBlendInterval = 1.0; |
|
|
|
m_ViewSmoothingData.pitchLockData.flLockInterval = 1.5; |
|
m_ViewSmoothingData.pitchLockData.flUnlockBlendInterval = 1.0; |
|
|
|
m_nFirstStep = 0; |
|
m_nStepCount = 0; |
|
m_SplashTime.Init( 60 ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Deconstructor |
|
//----------------------------------------------------------------------------- |
|
C_PropAirboat::~C_PropAirboat() |
|
{ |
|
if (m_pHeadlight) |
|
{ |
|
delete m_pHeadlight; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Draws the ammo for the airboat |
|
//----------------------------------------------------------------------------- |
|
int C_PropAirboat::GetPrimaryAmmoType() const |
|
{ |
|
if ( m_nAmmoCount < 0 ) |
|
return -1; |
|
|
|
int nAmmoType = GetAmmoDef()->Index( "AirboatGun" ); |
|
return nAmmoType; |
|
} |
|
|
|
int C_PropAirboat::GetPrimaryAmmoCount() const |
|
{ |
|
return m_nAmmoCount; |
|
} |
|
|
|
bool C_PropAirboat::PrimaryAmmoUsesClips() const |
|
{ |
|
return false; |
|
} |
|
|
|
int C_PropAirboat::GetPrimaryAmmoClip() const |
|
{ |
|
return -1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// The airboat prefers a more peppy response curve for joystick control. |
|
//----------------------------------------------------------------------------- |
|
int C_PropAirboat::GetJoystickResponseCurve() const |
|
{ |
|
return airboat_joy_response_move.GetInt(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Draws crosshair in the forward direction of the boat |
|
//----------------------------------------------------------------------------- |
|
void C_PropAirboat::DrawHudElements( ) |
|
{ |
|
BaseClass::DrawHudElements(); |
|
|
|
MDLCACHE_CRITICAL_SECTION(); |
|
|
|
CHudTexture *pIcon = gHUD.GetIcon( IsX360() ? "crosshair_default" : "plushair" ); |
|
if ( pIcon != NULL ) |
|
{ |
|
float x, y; |
|
Vector screen; |
|
|
|
int vx, vy, vw, vh; |
|
vgui::surface()->GetFullscreenViewport( vx, vy, vw, vh ); |
|
float screenWidth = vw; |
|
float screenHeight = vh; |
|
|
|
x = screenWidth/2; |
|
y = screenHeight/2; |
|
|
|
int eyeAttachmentIndex = LookupAttachment( "vehicle_driver_eyes" ); |
|
Vector vehicleEyeOrigin; |
|
QAngle vehicleEyeAngles; |
|
GetAttachment( eyeAttachmentIndex, vehicleEyeOrigin, vehicleEyeAngles ); |
|
|
|
// Only worry about yaw. |
|
vehicleEyeAngles.x = vehicleEyeAngles.z = 0.0f; |
|
|
|
Vector vecForward; |
|
AngleVectors( vehicleEyeAngles, &vecForward ); |
|
VectorMA( vehicleEyeOrigin, 100.0f, vecForward, vehicleEyeOrigin ); |
|
|
|
ScreenTransform( vehicleEyeOrigin, screen ); |
|
x += 0.5 * screen[0] * screenWidth + 0.5; |
|
y -= 0.5 * screen[1] * screenHeight + 0.5; |
|
|
|
x -= pIcon->Width() / 2; |
|
y -= pIcon->Height() / 2; |
|
|
|
pIcon->DrawSelf( x, y, gHUD.m_clrNormal ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Blend view angles. |
|
//----------------------------------------------------------------------------- |
|
void C_PropAirboat::UpdateViewAngles( C_BasePlayer *pLocalPlayer, CUserCmd *pCmd ) |
|
{ |
|
if ( r_AirboatViewBlendTo.GetInt() ) |
|
{ |
|
// |
|
// Autocenter the view after a period of no mouse movement while throttling. |
|
// |
|
bool bResetViewAngleTime = false; |
|
|
|
if ( ( pCmd->mousedx != 0 || pCmd->mousedy != 0 ) || ( fabsf( m_flThrottle ) < 0.01f ) ) |
|
{ |
|
if ( IsX360() ) |
|
{ |
|
// Only reset this if there isn't an autoaim target! |
|
C_BaseHLPlayer *pLocalHLPlayer = (C_BaseHLPlayer *)pLocalPlayer; |
|
if ( pLocalHLPlayer ) |
|
{ |
|
// Get the autoaim target. |
|
CBaseEntity *pTarget = pLocalHLPlayer->m_HL2Local.m_hAutoAimTarget.Get(); |
|
|
|
if( !pTarget ) |
|
{ |
|
bResetViewAngleTime = true; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
bResetViewAngleTime = true; |
|
} |
|
} |
|
|
|
if( bResetViewAngleTime ) |
|
{ |
|
m_flViewAngleDeltaTime = 0.0f; |
|
} |
|
else |
|
{ |
|
m_flViewAngleDeltaTime += gpGlobals->frametime; |
|
} |
|
|
|
if ( m_flViewAngleDeltaTime > r_AirboatViewBlendToTime.GetFloat() ) |
|
{ |
|
// Blend the view angles. |
|
int eyeAttachmentIndex = LookupAttachment( "vehicle_driver_eyes" ); |
|
Vector vehicleEyeOrigin; |
|
QAngle vehicleEyeAngles; |
|
GetAttachmentLocal( eyeAttachmentIndex, vehicleEyeOrigin, vehicleEyeAngles ); |
|
|
|
QAngle outAngles; |
|
InterpolateAngles( pCmd->viewangles, vehicleEyeAngles, outAngles, r_AirboatViewBlendToScale.GetFloat() ); |
|
pCmd->viewangles = outAngles; |
|
} |
|
} |
|
|
|
BaseClass::UpdateViewAngles( pLocalPlayer, pCmd ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void C_PropAirboat::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 C_PropAirboat::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 C_PropAirboat::DampenForwardMotion( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles, float flFrameTime ) |
|
{ |
|
// vecVehicleEyePos = real eye position this frame |
|
|
|
// m_vecLastEyePos = eye position last frame |
|
// m_vecEyeSpeed = eye speed last frame |
|
// vecPredEyePos = predicted eye position this frame (assuming no acceleration - it will get that from the pd controller). |
|
// vecPredEyeSpeed = predicted eye speed |
|
Vector vecPredEyePos = m_vecLastEyePos + m_vecEyeSpeed * flFrameTime; |
|
Vector vecPredEyeSpeed = m_vecEyeSpeed; |
|
|
|
// m_vecLastEyeTarget = real eye position last frame (used for speed calculation). |
|
// Calculate the approximate speed based on the current vehicle eye position and the eye position last frame. |
|
Vector vecVehicleEyeSpeed = ( vecVehicleEyePos - m_vecLastEyeTarget ) / flFrameTime; |
|
m_vecLastEyeTarget = vecVehicleEyePos; |
|
if (vecVehicleEyeSpeed.Length() == 0.0) |
|
{ |
|
return; |
|
} |
|
|
|
// Calculate the delta between the predicted eye position and speed and the current eye position and speed. |
|
Vector vecDeltaSpeed = vecVehicleEyeSpeed - vecPredEyeSpeed; |
|
Vector vecDeltaPos = vecVehicleEyePos - vecPredEyePos; |
|
|
|
// Forward vector. |
|
Vector vecForward; |
|
AngleVectors( vecVehicleEyeAngles, &vecForward ); |
|
|
|
float flDeltaLength = vecDeltaPos.Length(); |
|
if ( flDeltaLength > AIRBOAT_DELTA_LENGTH_MAX ) |
|
{ |
|
// Clamp. |
|
float flDelta = flDeltaLength - AIRBOAT_DELTA_LENGTH_MAX; |
|
if ( flDelta > 40.0f ) |
|
{ |
|
// This part is a bit of a hack to get rid of large deltas (at level load, etc.). |
|
m_vecLastEyePos = vecVehicleEyePos; |
|
m_vecEyeSpeed = vecVehicleEyeSpeed; |
|
} |
|
else |
|
{ |
|
// Position clamp. |
|
float flRatio = AIRBOAT_DELTA_LENGTH_MAX / flDeltaLength; |
|
vecDeltaPos *= flRatio; |
|
Vector vecForwardOffset = vecForward * ( vecForward.Dot( vecDeltaPos ) ); |
|
vecVehicleEyePos -= vecForwardOffset; |
|
m_vecLastEyePos = vecVehicleEyePos; |
|
|
|
// Speed clamp. |
|
vecDeltaSpeed *= flRatio; |
|
float flCoefficients[2]; |
|
ComputePDControllerCoefficients( flCoefficients, r_AirboatViewDampenFreq.GetFloat(), r_AirboatViewDampenDamp.GetFloat(), flFrameTime ); |
|
m_vecEyeSpeed += ( ( flCoefficients[0] * vecDeltaPos + flCoefficients[1] * vecDeltaSpeed ) * flFrameTime ); |
|
} |
|
} |
|
else |
|
{ |
|
// Generate an updated (dampening) speed for use in next frames position prediction. |
|
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 = vecPredEyePos; |
|
|
|
// Move eye forward/backward. |
|
Vector vecForwardOffset = vecForward * ( vecForward.Dot( vecDeltaPos ) ); |
|
vecVehicleEyePos -= vecForwardOffset; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void C_PropAirboat::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 C_PropAirboat::OnEnteredVehicle( C_BasePlayer *pPlayer ) |
|
{ |
|
int eyeAttachmentIndex = LookupAttachment( "vehicle_driver_eyes" ); |
|
Vector vehicleEyeOrigin; |
|
QAngle vehicleEyeAngles; |
|
GetAttachment( eyeAttachmentIndex, vehicleEyeOrigin, vehicleEyeAngles ); |
|
|
|
m_vecLastEyeTarget = vehicleEyeOrigin; |
|
m_vecLastEyePos = vehicleEyeOrigin; |
|
m_vecEyeSpeed = vec3_origin; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void C_PropAirboat::Simulate() |
|
{ |
|
UpdateHeadlight(); |
|
UpdateWake(); |
|
|
|
BaseClass::Simulate(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Creates, destroys, and updates the headlight effect as needed. |
|
//----------------------------------------------------------------------------- |
|
void C_PropAirboat::UpdateHeadlight() |
|
{ |
|
if (m_bHeadlightIsOn) |
|
{ |
|
if (!m_pHeadlight) |
|
{ |
|
// Turned on the headlight; create it. |
|
m_pHeadlight = new CHeadlightEffect(); |
|
|
|
if (!m_pHeadlight) |
|
return; |
|
|
|
m_pHeadlight->TurnOn(); |
|
} |
|
|
|
// The headlight is emitted from an attachment point so that it can move |
|
// as we turn the handlebars. |
|
int nHeadlightIndex = LookupAttachment( "vehicle_headlight" ); |
|
|
|
Vector vecLightPos; |
|
QAngle angLightDir; |
|
GetAttachment(nHeadlightIndex, vecLightPos, angLightDir); |
|
|
|
Vector vecLightDir, vecLightRight, vecLightUp; |
|
AngleVectors( angLightDir, &vecLightDir, &vecLightRight, &vecLightUp ); |
|
|
|
// Update the light with the new position and direction. |
|
m_pHeadlight->UpdateLight( vecLightPos, vecLightDir, vecLightRight, vecLightUp, HEADLIGHT_DISTANCE ); |
|
} |
|
else if (m_pHeadlight) |
|
{ |
|
// Turned off the headlight; delete it. |
|
delete m_pHeadlight; |
|
m_pHeadlight = NULL; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void C_PropAirboat::UpdateWake( void ) |
|
{ |
|
if ( gpGlobals->frametime <= 0.0f ) |
|
return; |
|
|
|
// Can't update too quickly |
|
if ( m_flUpdateTime > gpGlobals->curtime ) |
|
return; |
|
|
|
Vector screenPos = GetRenderOrigin(); |
|
screenPos.z = m_nExactWaterLevel; |
|
|
|
TrailPoint_t *pLast = m_nStepCount ? GetTrailPoint( m_nStepCount-1 ) : NULL; |
|
if ( ( pLast == NULL ) || ( pLast->m_vecScreenPos.DistToSqr( screenPos ) > 4.0f ) ) |
|
{ |
|
// If we're over our limit, steal the last point and put it up front |
|
if ( m_nStepCount >= MAX_WAKE_POINTS ) |
|
{ |
|
--m_nStepCount; |
|
++m_nFirstStep; |
|
} |
|
|
|
// Save off its screen position, not its world position |
|
TrailPoint_t *pNewPoint = GetTrailPoint( m_nStepCount ); |
|
pNewPoint->m_vecScreenPos = screenPos + Vector( 0, 0, 2 ); |
|
pNewPoint->m_flDieTime = gpGlobals->curtime + WAKE_LIFETIME; |
|
pNewPoint->m_flWidthVariance = random->RandomFloat( -16, 16 ); |
|
|
|
if ( pLast ) |
|
{ |
|
pNewPoint->m_flTexCoord = pLast->m_flTexCoord + pLast->m_vecScreenPos.DistTo( screenPos ); |
|
pNewPoint->m_flTexCoord = fmod( pNewPoint->m_flTexCoord, 1 ); |
|
} |
|
else |
|
{ |
|
pNewPoint->m_flTexCoord = 0.0f; |
|
} |
|
|
|
++m_nStepCount; |
|
} |
|
|
|
// Don't update again for a bit |
|
m_flUpdateTime = gpGlobals->curtime + ( 0.5f / (float) MAX_WAKE_POINTS ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &beamSeg - |
|
//----------------------------------------------------------------------------- |
|
void C_PropAirboat::DrawSegment( const BeamSeg_t &beamSeg, const Vector &vNormal ) |
|
{ |
|
// Build the endpoints. |
|
Vector vPoint1, vPoint2; |
|
VectorMA( beamSeg.m_vPos, beamSeg.m_flWidth*0.5f, vNormal, vPoint1 ); |
|
VectorMA( beamSeg.m_vPos, -beamSeg.m_flWidth*0.5f, vNormal, vPoint2 ); |
|
|
|
// Specify the points. |
|
m_Mesh.Position3fv( vPoint1.Base() ); |
|
m_Mesh.Color4f( VectorExpand( beamSeg.m_vColor ), beamSeg.m_flAlpha ); |
|
m_Mesh.TexCoord2f( 0, 0, beamSeg.m_flTexCoord ); |
|
m_Mesh.TexCoord2f( 1, 0, beamSeg.m_flTexCoord ); |
|
m_Mesh.AdvanceVertex(); |
|
|
|
m_Mesh.Position3fv( vPoint2.Base() ); |
|
m_Mesh.Color4f( VectorExpand( beamSeg.m_vColor ), beamSeg.m_flAlpha ); |
|
m_Mesh.TexCoord2f( 0, 1, beamSeg.m_flTexCoord ); |
|
m_Mesh.TexCoord2f( 1, 1, beamSeg.m_flTexCoord ); |
|
m_Mesh.AdvanceVertex(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : position - |
|
//----------------------------------------------------------------------------- |
|
void C_PropAirboat::DrawPontoonSplash( Vector origin, Vector direction, float speed ) |
|
{ |
|
Vector offset; |
|
|
|
CSmartPtr<CSplashParticle> pSimple = CSplashParticle::Create( "splish" ); |
|
pSimple->SetSortOrigin( origin ); |
|
|
|
SimpleParticle *pParticle; |
|
|
|
Vector color = Vector( 0.8f, 0.8f, 0.75f ); |
|
float colorRamp; |
|
|
|
float flScale = RemapVal( speed, 64, 256, 0.75f, 1.0f ); |
|
|
|
PMaterialHandle hMaterial; |
|
|
|
float tempDelta = gpGlobals->frametime; |
|
|
|
while( m_SplashTime.NextEvent( tempDelta ) ) |
|
{ |
|
if ( random->RandomInt( 0, 1 ) ) |
|
{ |
|
hMaterial = ParticleMgr()->GetPMaterial( "effects/splash1" ); |
|
} |
|
else |
|
{ |
|
hMaterial = ParticleMgr()->GetPMaterial( "effects/splash2" ); |
|
} |
|
|
|
offset = RandomVector( -8.0f * flScale, 8.0f * flScale ); |
|
offset[2] = 0.0f; |
|
offset += origin; |
|
|
|
pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), hMaterial, offset ); |
|
|
|
if ( pParticle == NULL ) |
|
continue; |
|
|
|
pParticle->m_flLifetime = 0.0f; |
|
pParticle->m_flDieTime = 0.25f; |
|
|
|
pParticle->m_vecVelocity.Random( -0.4f, 0.4f ); |
|
pParticle->m_vecVelocity += (direction*5.0f+Vector(0,0,1)); |
|
|
|
VectorNormalize( pParticle->m_vecVelocity ); |
|
|
|
pParticle->m_vecVelocity *= speed + random->RandomFloat( -128.0f, 128.0f ); |
|
|
|
colorRamp = random->RandomFloat( 0.75f, 1.25f ); |
|
|
|
pParticle->m_uchColor[0] = MIN( 1.0f, color[0] * colorRamp ) * 255.0f; |
|
pParticle->m_uchColor[1] = MIN( 1.0f, color[1] * colorRamp ) * 255.0f; |
|
pParticle->m_uchColor[2] = MIN( 1.0f, color[2] * colorRamp ) * 255.0f; |
|
|
|
pParticle->m_uchStartSize = random->RandomFloat( 8, 16 ) * flScale; |
|
pParticle->m_uchEndSize = pParticle->m_uchStartSize * 2; |
|
|
|
pParticle->m_uchStartAlpha = 255; |
|
pParticle->m_uchEndAlpha = 0; |
|
|
|
pParticle->m_flRoll = random->RandomInt( 0, 360 ); |
|
pParticle->m_flRollDelta = random->RandomFloat( -4.0f, 4.0f ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : Vector startPos - |
|
// wakeDir - |
|
// wakeLength - |
|
//----------------------------------------------------------------------------- |
|
void C_PropAirboat::DrawPontoonWake( Vector startPos, Vector wakeDir, float wakeLength, float speed ) |
|
{ |
|
#define WAKE_STEPS 6 |
|
|
|
Vector wakeStep = wakeDir * ( wakeLength / (float) WAKE_STEPS ); |
|
Vector origin; |
|
float scale; |
|
|
|
IMaterial *pMaterial = materials->FindMaterial( "effects/splashwake1", NULL, false ); |
|
CMatRenderContextPtr pRenderContext( materials ); |
|
IMesh* pMesh = pRenderContext->GetDynamicMesh( 0, 0, 0, pMaterial ); |
|
|
|
CMeshBuilder meshBuilder; |
|
meshBuilder.Begin( pMesh, MATERIAL_QUADS, WAKE_STEPS ); |
|
|
|
for ( int i = 0; i < WAKE_STEPS; i++ ) |
|
{ |
|
origin = startPos + ( wakeStep * i ); |
|
origin[0] += random->RandomFloat( -4.0f, 4.0f ); |
|
origin[1] += random->RandomFloat( -4.0f, 4.0f ); |
|
origin[2] = m_nExactWaterLevel + 2.0f; |
|
|
|
float scaleRange = RemapVal( i, 0, WAKE_STEPS-1, 32, 64 ); |
|
scale = scaleRange + ( 8.0f * sin( gpGlobals->curtime * 5 * i ) ); |
|
|
|
float alpha = RemapValClamped( speed, 128, 600, 0.05f, 0.25f ); |
|
float color[4] = { 1.0f, 1.0f, 1.0f, alpha }; |
|
|
|
// Needs to be time based so it'll freeze when the game is frozen |
|
float yaw = random->RandomFloat( 0, 360 ); |
|
|
|
Vector rRight = ( Vector(1,0,0) * cos( DEG2RAD( yaw ) ) ) - ( Vector(0,1,0) * sin( DEG2RAD( yaw ) ) ); |
|
Vector rUp = ( Vector(1,0,0) * cos( DEG2RAD( yaw+90.0f ) ) ) - ( Vector(0,1,0) * sin( DEG2RAD( yaw+90.0f ) ) ); |
|
|
|
Vector point; |
|
meshBuilder.Color4fv (color); |
|
meshBuilder.TexCoord2f (0, 0, 1); |
|
VectorMA (origin, -scale, rRight, point); |
|
VectorMA (point, -scale, rUp, point); |
|
meshBuilder.Position3fv (point.Base()); |
|
meshBuilder.AdvanceVertex(); |
|
|
|
meshBuilder.Color4fv (color); |
|
meshBuilder.TexCoord2f (0, 0, 0); |
|
VectorMA (origin, scale, rRight, point); |
|
VectorMA (point, -scale, rUp, point); |
|
meshBuilder.Position3fv (point.Base()); |
|
meshBuilder.AdvanceVertex(); |
|
|
|
meshBuilder.Color4fv (color); |
|
meshBuilder.TexCoord2f (0, 1, 0); |
|
VectorMA (origin, scale, rRight, point); |
|
VectorMA (point, scale, rUp, point); |
|
meshBuilder.Position3fv (point.Base()); |
|
meshBuilder.AdvanceVertex(); |
|
|
|
meshBuilder.Color4fv (color); |
|
meshBuilder.TexCoord2f (0, 1, 1); |
|
VectorMA (origin, -scale, rRight, point); |
|
VectorMA (point, scale, rUp, point); |
|
meshBuilder.Position3fv (point.Base()); |
|
meshBuilder.AdvanceVertex(); |
|
} |
|
|
|
meshBuilder.End(); |
|
pMesh->Draw(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int C_PropAirboat::DrawWake( void ) |
|
{ |
|
if ( cl_draw_airboat_wake.GetBool() == false ) |
|
return 0; |
|
|
|
// Make sure we're in water... |
|
if ( GetWaterLevel() == 0 ) |
|
return 0; |
|
|
|
//FIXME: For now, we don't draw slime this way |
|
if ( GetWaterLevel() == 2 ) |
|
return 0; |
|
|
|
bool bDriven = ( GetPassenger( VEHICLE_ROLE_DRIVER ) != NULL ); |
|
|
|
Vector vehicleDir = m_vecPhysVelocity; |
|
float vehicleSpeed = VectorNormalize( vehicleDir ); |
|
|
|
Vector vecPontoonFrontLeft; |
|
Vector vecPontoonFrontRight; |
|
Vector vecPontoonRearLeft; |
|
Vector vecPontoonRearRight; |
|
Vector vecSplashPoint; |
|
|
|
QAngle fooAngles; |
|
|
|
//FIXME: This lookup should be cached off |
|
// Get all attachments |
|
GetAttachment( LookupAttachment( "raytrace_fl" ), vecPontoonFrontLeft, fooAngles ); |
|
GetAttachment( LookupAttachment( "raytrace_fr" ), vecPontoonFrontRight, fooAngles ); |
|
GetAttachment( LookupAttachment( "raytrace_rl" ), vecPontoonRearLeft, fooAngles ); |
|
GetAttachment( LookupAttachment( "raytrace_rr" ), vecPontoonRearRight, fooAngles ); |
|
GetAttachment( LookupAttachment( "splash_pt" ), vecSplashPoint, fooAngles ); |
|
|
|
// Find the direction of the pontoons |
|
Vector vecLeftWakeDir = ( vecPontoonRearLeft - vecPontoonFrontLeft ); |
|
Vector vecRightWakeDir = ( vecPontoonRearRight - vecPontoonFrontRight ); |
|
|
|
// Find the pontoon's size |
|
float flWakeLeftLength = VectorNormalize( vecLeftWakeDir ); |
|
float flWakeRightLength = VectorNormalize( vecRightWakeDir ); |
|
|
|
vecPontoonFrontLeft.z = m_nExactWaterLevel; |
|
vecPontoonFrontRight.z = m_nExactWaterLevel; |
|
|
|
if ( bDriven && vehicleSpeed > 128.0f ) |
|
{ |
|
DrawPontoonWake( vecPontoonFrontLeft, vecLeftWakeDir, flWakeLeftLength, vehicleSpeed ); |
|
DrawPontoonWake( vecPontoonFrontRight, vecRightWakeDir, flWakeRightLength, vehicleSpeed ); |
|
|
|
Vector vecSplashDir; |
|
Vector vForward; |
|
GetVectors( &vForward, NULL, NULL ); |
|
|
|
if ( m_vecPhysVelocity.x < -64.0f ) |
|
{ |
|
vecSplashDir = vecLeftWakeDir - vForward; |
|
VectorNormalize( vecSplashDir ); |
|
|
|
// Don't do this if we're going backwards |
|
if ( m_vecPhysVelocity.y > 0.0f ) |
|
{ |
|
DrawPontoonSplash( vecPontoonFrontLeft + ( vecLeftWakeDir * 1.0f ), vecSplashDir, m_vecPhysVelocity.y ); |
|
} |
|
} |
|
else if ( m_vecPhysVelocity.x > 64.0f ) |
|
{ |
|
vecSplashDir = vecRightWakeDir + vForward; |
|
VectorNormalize( vecSplashDir ); |
|
|
|
// Don't do this if we're going backwards |
|
if ( m_vecPhysVelocity.y > 0.0f ) |
|
{ |
|
DrawPontoonSplash( vecPontoonFrontRight + ( vecRightWakeDir * 1.0f ), vecSplashDir, m_vecPhysVelocity.y ); |
|
} |
|
} |
|
} |
|
|
|
// Must have at least one point |
|
if ( m_nStepCount <= 1 ) |
|
return 1; |
|
|
|
IMaterial *pMaterial = materials->FindMaterial( "effects/splashwake4", 0); |
|
|
|
//Bind the material |
|
CMatRenderContextPtr pRenderContext( materials ); |
|
IMesh *pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, pMaterial ); |
|
|
|
m_Mesh.Begin( pMesh, MATERIAL_TRIANGLE_STRIP, (m_nStepCount-1) * 2 ); |
|
|
|
TrailPoint_t *pLast = GetTrailPoint( m_nStepCount - 1 ); |
|
|
|
TrailPoint_t currentPoint; |
|
currentPoint.m_flDieTime = gpGlobals->curtime + 0.5f; |
|
currentPoint.m_vecScreenPos = GetAbsOrigin(); |
|
currentPoint.m_vecScreenPos[2] = m_nExactWaterLevel + 16; |
|
currentPoint.m_flTexCoord = pLast->m_flTexCoord + currentPoint.m_vecScreenPos.DistTo(pLast->m_vecScreenPos); |
|
currentPoint.m_flTexCoord = fmod( currentPoint.m_flTexCoord, 1 ); |
|
currentPoint.m_flWidthVariance = 0.0f; |
|
|
|
TrailPoint_t *pPrevPoint = NULL; |
|
|
|
Vector segDir, normal; |
|
|
|
for ( int i = 0; i <= m_nStepCount; ++i ) |
|
{ |
|
// This makes it so that we're always drawing to the current location |
|
TrailPoint_t *pPoint = (i != m_nStepCount) ? GetTrailPoint(i) : ¤tPoint; |
|
|
|
float flLifePerc = RemapValClamped( ( pPoint->m_flDieTime - gpGlobals->curtime ), 0, WAKE_LIFETIME, 0.0f, 1.0f ); |
|
|
|
BeamSeg_t curSeg; |
|
curSeg.m_vColor.x = curSeg.m_vColor.y = curSeg.m_vColor.z = 1.0f; |
|
|
|
float flAlphaFade = flLifePerc; |
|
float alpha = RemapValClamped( fabs( m_vecPhysVelocity.y ), 128, 600, 0.0f, 1.0f ); |
|
|
|
curSeg.m_flAlpha = 0.25f; |
|
curSeg.m_flAlpha *= flAlphaFade * alpha; |
|
|
|
curSeg.m_vPos = pPoint->m_vecScreenPos; |
|
|
|
float widthBase = SimpleSplineRemapVal( fabs( m_vecPhysVelocity.y ), 128, 600, 32, 48 ); |
|
|
|
curSeg.m_flWidth = Lerp( flLifePerc, widthBase*6, widthBase ); |
|
curSeg.m_flWidth += pPoint->m_flWidthVariance; |
|
|
|
if ( curSeg.m_flWidth < 0.0f ) |
|
{ |
|
curSeg.m_flWidth = 0.0f; |
|
} |
|
|
|
curSeg.m_flTexCoord = pPoint->m_flTexCoord; |
|
|
|
if ( pPrevPoint != NULL ) |
|
{ |
|
segDir = ( pPrevPoint->m_vecScreenPos - pPoint->m_vecScreenPos ); |
|
VectorNormalize( segDir ); |
|
|
|
normal = CrossProduct( segDir, Vector( 0, 0, -1 ) ); |
|
|
|
DrawSegment( curSeg, normal ); |
|
} |
|
|
|
pPrevPoint = pPoint; |
|
} |
|
|
|
m_Mesh.End(); |
|
pMesh->Draw(); |
|
|
|
return 1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : flags - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int C_PropAirboat::DrawModel( int flags ) |
|
{ |
|
if ( BaseClass::DrawModel( flags ) == false ) |
|
return 0; |
|
|
|
if ( !m_bReadyToDraw ) |
|
return 0; |
|
|
|
return DrawWake(); |
|
}
|
|
|