summaryrefslogtreecommitdiff
path: root/game/shared/tf/tf_gamemovement.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/shared/tf/tf_gamemovement.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/shared/tf/tf_gamemovement.cpp')
-rw-r--r--game/shared/tf/tf_gamemovement.cpp3558
1 files changed, 3558 insertions, 0 deletions
diff --git a/game/shared/tf/tf_gamemovement.cpp b/game/shared/tf/tf_gamemovement.cpp
new file mode 100644
index 0000000..86c7087
--- /dev/null
+++ b/game/shared/tf/tf_gamemovement.cpp
@@ -0,0 +1,3558 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+#include "cbase.h"
+#include "gamemovement.h"
+#include "tf_gamerules.h"
+#include "tf_shareddefs.h"
+#include "in_buttons.h"
+#include "movevars_shared.h"
+#include "collisionutils.h"
+#include "debugoverlay_shared.h"
+#include "baseobject_shared.h"
+#include "particle_parse.h"
+#include "baseobject_shared.h"
+#include "coordsize.h"
+#include "tf_weapon_medigun.h"
+#include "tf_wearable_item_demoshield.h"
+#include "takedamageinfo.h"
+#include "tf_weapon_buff_item.h"
+#include "halloween/tf_weapon_spellbook.h"
+
+#ifdef CLIENT_DLL
+ #include "c_tf_player.h"
+ #include "c_world.h"
+ #include "c_team.h"
+ #include "prediction.h"
+
+ #define CTeam C_Team
+
+#else
+ #include "tf_player.h"
+ #include "team.h"
+ #include "bot/tf_bot.h"
+ #include "tf_fx.h"
+#endif
+
+
+ConVar tf_duck_debug_spew( "tf_duck_debug_spew", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
+ConVar tf_showspeed( "tf_showspeed", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
+ConVar tf_avoidteammates( "tf_avoidteammates", "1", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Controls how teammates interact when colliding.\n 0: Teammates block each other\n 1: Teammates pass through each other, but push each other away (default)" );
+ConVar tf_avoidteammates_pushaway( "tf_avoidteammates_pushaway", "1", FCVAR_REPLICATED, "Whether or not teammates push each other away when occupying the same space" );
+ConVar tf_solidobjects( "tf_solidobjects", "1", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
+ConVar tf_clamp_back_speed( "tf_clamp_back_speed", "0.9", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
+ConVar tf_clamp_back_speed_min( "tf_clamp_back_speed_min", "100", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
+ConVar tf_clamp_airducks( "tf_clamp_airducks", "1", FCVAR_REPLICATED );
+ConVar tf_resolve_stuck_players( "tf_resolve_stuck_players", "1", FCVAR_REPLICATED );
+ConVar tf_scout_hype_mod( "tf_scout_hype_mod", "55", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
+ConVar tf_max_charge_speed( "tf_max_charge_speed", "750", FCVAR_NOTIFY | FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
+ConVar tf_parachute_gravity( "tf_parachute_gravity", "0.2f", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Gravity while parachute is deployed" );
+ConVar tf_parachute_maxspeed_xy( "tf_parachute_maxspeed_xy", "400.0f", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Max XY Speed while Parachute is deployed" );
+ConVar tf_parachute_maxspeed_z( "tf_parachute_maxspeed_z", "-100.0f", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Max Z Speed while Parachute is deployed" );
+ConVar tf_parachute_maxspeed_onfire_z( "tf_parachute_maxspeed_onfire_z", "10.0f", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Max Z Speed when on Fire and Parachute is deployed" );
+ConVar tf_parachute_aircontrol( "tf_parachute_aircontrol", "2.5f", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Multiplier for how much air control players have when Parachute is deployed" );
+
+ConVar tf_halloween_kart_aircontrol( "tf_halloween_kart_aircontrol", "1.2f", FCVAR_CHEAT | FCVAR_REPLICATED, "Multiplier for how much air control players have when in Kart Mode" );
+ConVar tf_ghost_up_speed( "tf_ghost_up_speed", "300.f", FCVAR_CHEAT | FCVAR_REPLICATED, "Speed that ghost go upward while holding jump key" );
+ConVar tf_ghost_xy_speed( "tf_ghost_xy_speed", "300.f", FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar tf_grapplinghook_move_speed( "tf_grapplinghook_move_speed", "750", FCVAR_REPLICATED | FCVAR_CHEAT );
+ConVar tf_grapplinghook_use_acceleration( "tf_grapplinghook_use_acceleration", "0", FCVAR_REPLICATED, "Use full acceleration calculation for grappling hook movement" );
+ConVar tf_grapplinghook_acceleration( "tf_grapplinghook_acceleration", "3500", FCVAR_REPLICATED | FCVAR_CHEAT );
+ConVar tf_grapplinghook_dampening( "tf_grapplinghook_dampening", "500", FCVAR_REPLICATED | FCVAR_CHEAT );
+ConVar tf_grapplinghook_follow_distance( "tf_grapplinghook_follow_distance", "64", FCVAR_REPLICATED | FCVAR_CHEAT );
+ConVar tf_grapplinghook_jump_up_speed( "tf_grapplinghook_jump_up_speed", "375", FCVAR_REPLICATED | FCVAR_CHEAT );
+ConVar tf_grapplinghook_prevent_fall_damage( "tf_grapplinghook_prevent_fall_damage", "0", FCVAR_REPLICATED | FCVAR_CHEAT );
+ConVar tf_grapplinghook_medic_latch_speed_scale( "tf_grapplinghook_medic_latch_speed_scale", "0.65", FCVAR_REPLICATED | FCVAR_CHEAT );
+
+#ifdef STAGING_ONLY
+ConVar tf_movement_doubletap_window( "tf_movement_doubletap_window", "0.1f", FCVAR_REPLICATED | FCVAR_CHEAT );
+ConVar tf_space_gravity_jump_multipler( "tf_space_gravity_jump_multipler", "1.05", FCVAR_CHEAT | FCVAR_REPLICATED, "Multiplier for player jump velocity in space" );
+ConVar tf_space_aircontrol( "tf_space_aircontrol", "1.0", FCVAR_CHEAT | FCVAR_REPLICATED, "Multiplier for how much air control players have in space" );
+
+ConVar tf_taunt_move_speed( "tf_taunt_move_speed", "0", FCVAR_REPLICATED | FCVAR_CHEAT );
+#endif // STAGING_ONLY
+
+extern ConVar cl_forwardspeed;
+extern ConVar cl_backspeed;
+extern ConVar cl_sidespeed;
+extern ConVar mp_tournament_readymode_countdown;
+
+#define TF_MAX_SPEED (400 * 1.3) // 400 is Scout max speed, and we allow up to 3% movement bonus.
+
+#define TF_WATERJUMP_FORWARD 30
+#define TF_WATERJUMP_UP 300
+#define TF_TIME_TO_DUCK 0.3f
+#define TF_AIRDUCKED_COUNT 2
+//ConVar tf_waterjump_up( "tf_waterjump_up", "300", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
+//ConVar tf_waterjump_forward( "tf_waterjump_forward", "30", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
+
+#define NUM_CROUCH_HINTS 3
+
+class CTFGameMovement : public CGameMovement
+{
+public:
+ DECLARE_CLASS( CTFGameMovement, CGameMovement );
+
+ CTFGameMovement();
+
+ virtual void PlayerMove();
+ virtual unsigned int PlayerSolidMask( bool brushOnly = false );
+ virtual void ProcessMovement( CBasePlayer *pBasePlayer, CMoveData *pMove );
+ virtual bool CanAccelerate();
+ virtual bool CheckJumpButton();
+ virtual int CheckStuck( void );
+ virtual bool CheckWater( void );
+ virtual void WaterMove( void );
+ virtual void FullWalkMove();
+ virtual void WalkMove( void );
+ virtual void AirMove( void );
+ virtual void FullTossMove( void );
+ virtual void CategorizePosition( void );
+ virtual void CheckFalling( void );
+ virtual void Duck( void );
+ virtual Vector GetPlayerViewOffset( bool ducked ) const;
+ bool GrapplingHookMove( void );
+ bool ChargeMove( void );
+ bool StunMove( void );
+ bool TauntMove( void );
+ void VehicleMove( void );
+ bool HighMaxSpeedMove( void );
+ virtual float GetAirSpeedCap( void );
+
+ virtual void TracePlayerBBox( const Vector& start, const Vector& end, unsigned int fMask, int collisionGroup, trace_t& pm );
+ virtual CBaseHandle TestPlayerPosition( const Vector& pos, int collisionGroup, trace_t& pm );
+ virtual void StepMove( Vector &vecDestination, trace_t &trace );
+ virtual bool GameHasLadders() const;
+ virtual void SetGroundEntity( trace_t *pm );
+ virtual void PlayerRoughLandingEffects( float fvol );
+
+ virtual void HandleDuckingSpeedCrop( void );
+protected:
+
+ virtual void CheckWaterJump( void );
+ void FullWalkMoveUnderwater();
+
+private:
+
+ bool CheckWaterJumpButton( void );
+ void AirDash( void );
+ void PreventBunnyJumping();
+ void ToggleParachute( void );
+ void CheckKartWallBumping();
+
+ // Ducking.
+#if 0
+ // New duck tests!
+ void HandleDuck( int nButtonsPressed );
+ void HandleUnDuck( int nButtonsReleased );
+ void TestDuck();
+#endif
+ void DuckOverrides();
+ void OnDuck( int nButtonsPressed );
+ void OnUnDuck( int nButtonsReleased );
+
+#ifdef STAGING_ONLY
+ void CheckForDoubleTap( void );
+ void OnDoubleTapped( int nKey );
+ void TeleportMove( Vector &vecDirection, float flDist );
+
+ CUtlMap< int, float > m_MoveKeyDownTimes;
+ float m_flNextDoubleTapTeleportTime;
+#endif // STAGING_ONLY
+
+private:
+
+ Vector m_vecWaterPoint;
+ CTFPlayer *m_pTFPlayer;
+ bool m_isPassingThroughEnemies;
+
+ static float CalcWishSpeedThreshold()
+ {
+ return 100.0f * sv_friction.GetFloat() / (sv_accelerate.GetFloat());
+ }
+};
+
+
+// Expose our interface.
+static CTFGameMovement g_GameMovement;
+IGameMovement *g_pGameMovement = ( IGameMovement * )&g_GameMovement;
+
+EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CGameMovement, IGameMovement,INTERFACENAME_GAMEMOVEMENT, g_GameMovement );
+
+
+// ---------------------------------------------------------------------------------------- //
+// CTFGameMovement.
+// ---------------------------------------------------------------------------------------- //
+
+CTFGameMovement::CTFGameMovement()
+{
+ m_pTFPlayer = NULL;
+ m_isPassingThroughEnemies = false;
+
+#ifdef STAGING_ONLY
+ m_MoveKeyDownTimes.SetLessFunc( DefLessFunc (int) );
+ m_flNextDoubleTapTeleportTime = 0.f;
+#endif // STAGING_ONLY
+}
+
+//----------------------------------------------------------------------------------------
+// Purpose: moves the player
+//----------------------------------------------------------------------------------------
+void CTFGameMovement::PlayerMove()
+{
+ // call base class to do movement
+ BaseClass::PlayerMove();
+
+ // handle player's interaction with water
+ int nNewWaterLevel = m_pTFPlayer->GetWaterLevel();
+ if ( m_nOldWaterLevel != nNewWaterLevel )
+ {
+ if ( WL_NotInWater == m_nOldWaterLevel )
+ {
+ // The player has just entered the water. Determine if we should play a splash sound.
+ bool bPlaySplash = false;
+
+ Vector vecVelocity = m_pTFPlayer->GetAbsVelocity();
+ if ( vecVelocity.z <= -200.0f )
+ {
+ // If the player has significant downward velocity, play a splash regardless of water depth. (e.g. Jumping hard into a puddle)
+ bPlaySplash = true;
+ }
+ else
+ {
+ // Look at the water depth below the player. If it's significantly deep, play a splash to accompany the sinking that's about to happen.
+ Vector vecStart = m_pTFPlayer->GetAbsOrigin();
+ Vector vecEnd = vecStart;
+ vecEnd.z -= 20; // roughly thigh deep
+ trace_t tr;
+ // see if we hit anything solid a little bit below the player
+ UTIL_TraceLine( vecStart, vecEnd, MASK_SOLID,m_pTFPlayer, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction >= 1.0f )
+ {
+ // some amount of water below the player, play a splash
+ bPlaySplash = true;
+ }
+ }
+
+ if ( bPlaySplash )
+ {
+ m_pTFPlayer->EmitSound( "Physics.WaterSplash" );
+ }
+ }
+ }
+
+ // Remove our shield charge if we slow down a bunch.
+ float flSpeed = VectorLength( mv->m_vecVelocity );
+ if ( flSpeed < 300.0f )
+ {
+ m_pTFPlayer->m_Shared.EndCharge();
+ }
+}
+
+Vector CTFGameMovement::GetPlayerViewOffset( bool ducked ) const
+{
+ return ( ( ducked ) ? ( VEC_DUCK_VIEW_SCALED( m_pTFPlayer ) ) : ( m_pTFPlayer->GetClassEyeHeight() ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allow bots etc to use slightly different solid masks
+//-----------------------------------------------------------------------------
+unsigned int CTFGameMovement::PlayerSolidMask( bool brushOnly )
+{
+ unsigned int uMask = 0;
+
+ // Ghost players dont collide with anything but the world
+ if ( m_pTFPlayer && m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
+ {
+ return MASK_PLAYERSOLID_BRUSHONLY;
+ }
+
+ if ( m_pTFPlayer && !m_isPassingThroughEnemies )
+ {
+ switch( m_pTFPlayer->GetTeamNumber() )
+ {
+ case TF_TEAM_RED:
+ uMask = CONTENTS_BLUETEAM;
+ break;
+
+ case TF_TEAM_BLUE:
+ uMask = CONTENTS_REDTEAM;
+ break;
+ }
+ }
+
+ return ( uMask | BaseClass::PlayerSolidMask( brushOnly ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Overridden to allow players to run faster than the maxspeed
+//-----------------------------------------------------------------------------
+void CTFGameMovement::ProcessMovement( CBasePlayer *pBasePlayer, CMoveData *pMove )
+{
+ // Verify data.
+ Assert( pBasePlayer );
+ Assert( pMove );
+ if ( !pBasePlayer || !pMove )
+ return;
+
+ // Reset point contents for water check.
+ ResetGetPointContentsCache();
+
+ // Cropping movement speed scales mv->m_fForwardSpeed etc. globally
+ // Once we crop, we don't want to recursively crop again, so we set the crop
+ // flag globally here once per usercmd cycle.
+ m_iSpeedCropped = SPEED_CROPPED_RESET;
+
+ // Get the current TF player.
+ m_pTFPlayer = ToTFPlayer( pBasePlayer );
+ player = m_pTFPlayer;
+ mv = pMove;
+
+ // The max speed is currently set to the scout - if this changes we need to change this!
+ mv->m_flMaxSpeed = TF_MAX_SPEED;
+
+ // Handle charging demomens
+ ChargeMove();
+
+ // Handle player stun.
+ StunMove();
+
+ // Handle player taunt move
+ TauntMove();
+
+ // Handle grappling hook move
+ GrapplingHookMove();
+
+ // Handle scouts that can move really fast with buffs
+ HighMaxSpeedMove();
+
+ // Run the command.
+ PlayerMove();
+
+#ifdef STAGING_ONLY
+ CheckForDoubleTap();
+#endif // STAGING_ONLY
+
+ FinishMove();
+
+#if defined(GAME_DLL)
+ m_pTFPlayer->m_bTakenBlastDamageSinceLastMovement = false;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFGameMovement::GrapplingHookMove()
+{
+ CBaseEntity *pHookTarget = m_pTFPlayer->GetGrapplingHookTarget();
+
+ if ( !pHookTarget )
+ return false;
+
+ // check if player can be moved
+ if ( !m_pTFPlayer->CanPlayerMove() || m_pTFPlayer->m_Shared.IsControlStunned() )
+ {
+ mv->m_flForwardMove = 0.f;
+ mv->m_flSideMove = 0.f;
+ mv->m_flUpMove = 0.f;
+ mv->m_nButtons = 0;
+ m_pTFPlayer->m_nButtons = mv->m_nButtons;
+ return false;
+ }
+
+ m_pTFPlayer->SetGroundEntity( NULL );
+
+ Vector vDesiredMove = pHookTarget->WorldSpaceCenter() - m_pTFPlayer->WorldSpaceCenter();
+
+ CTFPlayer *pPlayerToCheckForRune = m_pTFPlayer;
+ if ( pHookTarget->IsPlayer() )
+ {
+ CTFPlayer *pHookedPlayer = ToTFPlayer( pHookTarget );
+ bool bFollowingAllyGrapple = false;
+ // If our target is grappling, adjust aim to behind them
+ CBaseEntity *pHookedPlayerTarget = pHookedPlayer->GetGrapplingHookTarget();
+ if ( pHookedPlayerTarget )
+ {
+ bFollowingAllyGrapple = pHookedPlayer->GetTeamNumber() == m_pTFPlayer->GetTeamNumber();
+ Vector vTargetGrapple = pHookedPlayerTarget->WorldSpaceCenter() - pHookedPlayer->WorldSpaceCenter();
+ vTargetGrapple.NormalizeInPlace();
+ vDesiredMove += vTargetGrapple * ( -1 * tf_grapplinghook_follow_distance.GetFloat() );
+ }
+ else
+ {
+ // Otherwise, aim short of their center.
+ vDesiredMove += vDesiredMove.Normalized() * ( -1 * tf_grapplinghook_follow_distance.GetFloat() );
+ }
+
+ if ( bFollowingAllyGrapple )
+ {
+ pPlayerToCheckForRune = pHookedPlayer;
+ }
+ }
+
+ mv->m_flMaxSpeed = tf_grapplinghook_move_speed.GetFloat();
+
+ // If we're grappling along with an ally, use their rune to avoid falling behind or passing them
+ if ( pPlayerToCheckForRune->m_Shared.GetCarryingRuneType() == RUNE_AGILITY )
+ {
+ mv->m_flMaxSpeed = 950.f;
+ }
+ // Heavies get a grapple speed reduction across the board, even if they have Agility
+ if ( pPlayerToCheckForRune->GetPlayerClass()->GetClassIndex() == TF_CLASS_HEAVYWEAPONS )
+ {
+ mv->m_flMaxSpeed *= 0.70f;
+ }
+ // Grapple movement speed penalty if player is carrying the flag and a powerup
+ else if ( pPlayerToCheckForRune->HasTheFlag() && pPlayerToCheckForRune->m_Shared.GetCarryingRuneType() != RUNE_NONE )
+ {
+ if ( pPlayerToCheckForRune->m_Shared.GetCarryingRuneType() == RUNE_AGILITY )
+ {
+ mv->m_flMaxSpeed *= 0.8f;
+ }
+ else
+ {
+ mv->m_flMaxSpeed *= 0.65f;
+ }
+ }
+ // Pyros that are hooked into enemy players travel slower because of their advantage in close quarters
+ else if ( pPlayerToCheckForRune->GetPlayerClass()->GetClassIndex() == TF_CLASS_PYRO && pPlayerToCheckForRune->m_Shared.InCond( TF_COND_GRAPPLED_TO_PLAYER ) )
+ {
+ mv->m_flMaxSpeed *= 0.7f;
+ }
+
+ // if the medic hook latched on to teammate, his movement should be slower to eventually detach from the healing target
+ // this requires medic to do something instead of getting a free ride (except medic with AGILITY rune)
+ if ( m_pTFPlayer->GetPlayerClass()->GetClassIndex() == TF_CLASS_MEDIC && pHookTarget->IsPlayer() && pHookTarget->InSameTeam( m_pTFPlayer ) && m_pTFPlayer->m_Shared.GetCarryingRuneType() != RUNE_AGILITY )
+ {
+ mv->m_flMaxSpeed *= tf_grapplinghook_medic_latch_speed_scale.GetFloat();
+ }
+
+ if ( tf_grapplinghook_use_acceleration.GetBool() )
+ {
+ // Use acceleration with dampening
+ float flSpeed = mv->m_vecVelocity.Length();
+ if ( flSpeed > 0.f ) {
+ float flDampen = Min( tf_grapplinghook_dampening.GetFloat() * gpGlobals->frametime, flSpeed );
+ mv->m_vecVelocity *= ( flSpeed - flDampen ) / flSpeed;
+ }
+
+ mv->m_vecVelocity += vDesiredMove.Normalized() * ( tf_grapplinghook_acceleration.GetFloat() * gpGlobals->frametime );
+
+ flSpeed = mv->m_vecVelocity.Length();
+ if ( flSpeed > mv->m_flMaxSpeed )
+ {
+ mv->m_vecVelocity *= mv->m_flMaxSpeed / flSpeed;
+ }
+ }
+ else
+ {
+ // Simple velocity calculation
+ float vDist = vDesiredMove.Length();
+ if ( vDist > mv->m_flMaxSpeed * gpGlobals->frametime )
+ {
+ mv->m_vecVelocity = vDesiredMove * ( mv->m_flMaxSpeed / vDist );
+ }
+ else
+ {
+ mv->m_vecVelocity = vDesiredMove / gpGlobals->frametime;
+ }
+ }
+
+ // slow down when player is close to the hook target to prevent yoyo effect
+ float flDistSqrToTarget = m_pTFPlayer->GetAbsOrigin().DistToSqr( pHookTarget->GetAbsOrigin() );
+ if ( flDistSqrToTarget < 10000 )
+ {
+ // remap the speed between 80-100 unit distance
+ mv->m_vecVelocity = mv->m_vecVelocity.Normalized() * RemapValClamped( flDistSqrToTarget, 6400, 10000, 0.f, mv->m_flMaxSpeed );
+ }
+
+ mv->m_flForwardMove = 0.f;
+ mv->m_flSideMove = 0.f;
+ mv->m_flUpMove = 0.f;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFGameMovement::ChargeMove()
+{
+ if ( !m_pTFPlayer->m_Shared.InCond( TF_COND_SHIELD_CHARGE ) )
+ {
+ // Check for Quick Fix Medic healing a charging player
+ if ( !m_pTFPlayer->IsPlayerClass( TF_CLASS_MEDIC ) )
+ return false;
+
+ CTFWeaponBase *pTFWeapon = m_pTFPlayer->GetActiveTFWeapon();
+ if ( !pTFWeapon )
+ return false;
+
+ if ( pTFWeapon->GetWeaponID() != TF_WEAPON_MEDIGUN )
+ return false;
+
+ CWeaponMedigun *pMedigun = static_cast< CWeaponMedigun* >( pTFWeapon );
+ if ( !pMedigun || pMedigun->GetMedigunType() != MEDIGUN_QUICKFIX )
+ return false;
+
+ CTFPlayer *pHealTarget = ToTFPlayer( pMedigun->GetHealTarget() );
+ if ( !pHealTarget || !pHealTarget->m_Shared.InCond( TF_COND_SHIELD_CHARGE ) )
+ return false;
+ }
+
+ mv->m_flMaxSpeed = tf_max_charge_speed.GetFloat();
+
+ int oldbuttons = mv->m_nButtons;
+
+ // Handle demoman shield charge.
+ mv->m_flForwardMove = tf_max_charge_speed.GetFloat();
+ mv->m_flSideMove = 0.0f;
+ mv->m_flUpMove = 0.0f;
+ if ( mv->m_nButtons & IN_ATTACK2 )
+ {
+ // Allow the player to continue to hold alt-fire.
+ mv->m_nButtons = IN_ATTACK2;
+ }
+ else
+ {
+ mv->m_nButtons = 0;
+ }
+
+ if ( oldbuttons & IN_ATTACK )
+ {
+ mv->m_nButtons |= IN_ATTACK;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFGameMovement::StunMove()
+{
+ // Handle control stun.
+ if ( m_pTFPlayer->m_Shared.IsControlStunned()
+ || m_pTFPlayer->m_Shared.IsLoserStateStunned() )
+ {
+ // Can't fire or select weapons.
+ if ( m_pTFPlayer->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) )
+ {
+ // Heavies can still spin their gun.
+ if ( mv->m_nButtons & IN_ATTACK2 || mv->m_nButtons & IN_ATTACK )
+ {
+ mv->m_nButtons = IN_ATTACK2; // Turn off all other buttons.
+ }
+ }
+ else
+ {
+ mv->m_nButtons = 0;
+ }
+
+ if ( m_pTFPlayer->m_Shared.IsControlStunned() )
+ {
+ mv->m_flForwardMove = 0.0f;
+ mv->m_flSideMove = 0.0f;
+ mv->m_flUpMove = 0.0f;
+ }
+
+ m_pTFPlayer->m_nButtons = mv->m_nButtons;
+ }
+
+ // Handle movement stuns
+ float flStunAmount = m_pTFPlayer->m_Shared.GetAmountStunned( TF_STUN_MOVEMENT );
+ // Lerp to the desired amount
+ if ( flStunAmount )
+ {
+ if ( m_pTFPlayer->m_Shared.m_flStunLerpTarget != flStunAmount )
+ {
+ m_pTFPlayer->m_Shared.m_flLastMovementStunChange = gpGlobals->curtime;
+ m_pTFPlayer->m_Shared.m_flStunLerpTarget = flStunAmount;
+ m_pTFPlayer->m_Shared.m_bStunNeedsFadeOut = true;
+ }
+
+ mv->m_flForwardMove *= 1.f - flStunAmount;
+ mv->m_flSideMove *= 1.f - flStunAmount;
+ if ( m_pTFPlayer->m_Shared.GetStunFlags() & TF_STUN_MOVEMENT_FORWARD_ONLY )
+ {
+ mv->m_flForwardMove = 0.f;
+ }
+
+ return true;
+ }
+ else if ( m_pTFPlayer->m_Shared.m_flLastMovementStunChange )
+ {
+ // Lerp out to normal speed
+ if ( m_pTFPlayer->m_Shared.m_bStunNeedsFadeOut )
+ {
+ m_pTFPlayer->m_Shared.m_flLastMovementStunChange = gpGlobals->curtime;
+ m_pTFPlayer->m_Shared.m_bStunNeedsFadeOut = false;
+ }
+
+ float flCurStun = RemapValClamped( (gpGlobals->curtime - m_pTFPlayer->m_Shared.m_flLastMovementStunChange), 0.2, 0.0, 0.0, 1.0 );
+ if ( flCurStun )
+ {
+ float flRemap = m_pTFPlayer->m_Shared.m_flStunLerpTarget * flCurStun;
+ mv->m_flForwardMove *= (1.0 - flRemap);
+ mv->m_flSideMove *= (1.0 - flRemap);
+ if ( m_pTFPlayer->m_Shared.GetStunFlags() & TF_STUN_MOVEMENT_FORWARD_ONLY )
+ {
+ mv->m_flForwardMove = 0.f;
+ }
+ }
+ else
+ {
+ m_pTFPlayer->m_Shared.m_flStunLerpTarget = 0.f;
+ m_pTFPlayer->m_Shared.m_flLastMovementStunChange = 0;
+ }
+
+ return true;
+ }
+
+ // No one can move when in a final countdown transition.
+ // Do this here to avoid the inevitable hack that prevents players
+ // from receiving a flag or condition by stalling thinks, etc.
+ if ( TFGameRules() && TFGameRules()->BInMatchStartCountdown() )
+ {
+ mv->m_flForwardMove = 0.f;
+ mv->m_flSideMove = 0.f;
+ mv->m_flUpMove = 0.f;
+ mv->m_nButtons = 0;
+ m_pTFPlayer->m_nButtons = mv->m_nButtons;
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFGameMovement::TauntMove( void )
+{
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
+ {
+ VehicleMove();
+ }
+ else if ( m_pTFPlayer->m_Shared.InCond( TF_COND_TAUNTING ) && m_pTFPlayer->CanMoveDuringTaunt() )
+ {
+ m_pTFPlayer->SetTauntYaw( mv->m_vecViewAngles[YAW] );
+
+ bool bForceMoveForward = m_pTFPlayer->IsTauntForceMovingForward();
+ float flMaxMoveSpeed = m_pTFPlayer->GetTauntMoveSpeed();
+ float flAcceleration = m_pTFPlayer->GetTauntMoveAcceleration();
+
+ float flMoveDir = 0.f;
+ if ( !bForceMoveForward )
+ {
+ // Grab analog inputs, normalized to [0,1], to allow controller to also drive taunt movement.
+ if ( mv->m_flForwardMove > 0 && cl_forwardspeed.GetFloat() > 0 )
+ {
+ flMoveDir += mv->m_flForwardMove / cl_forwardspeed.GetFloat();
+ }
+ else if ( mv->m_flForwardMove < 0 && cl_backspeed.GetFloat() > 0 )
+ {
+ flMoveDir += mv->m_flForwardMove / cl_backspeed.GetFloat();
+ }
+
+ // No need to read buttons explicitly anymore, since that input is already included in m_flForwardMove
+ /* if ( mv->m_nButtons & IN_FORWARD )
+ flMoveDir += 1.f;
+ if ( mv->m_nButtons & IN_BACK )
+ flMoveDir += -1.f; */
+
+ // Clamp to [0,1], just in case.
+ if ( flMoveDir > 1.0f )
+ {
+ flMoveDir = 1.0f;
+ }
+ else if ( flMoveDir < -1.0f )
+ {
+ flMoveDir = -1.0f;
+ }
+ }
+ else
+ {
+ flMoveDir = 1.f;
+ }
+
+ bool bMoving = flMoveDir != 0.f;
+ float flSign = bMoving ? 1.f : -1.f;
+#ifdef STAGING_ONLY
+ flMaxMoveSpeed = tf_taunt_move_speed.GetFloat() > 0.f ? tf_taunt_move_speed.GetFloat() : flMaxMoveSpeed;
+#endif // STAGING_ONLY
+ if ( flAcceleration > 0.f )
+ {
+ m_pTFPlayer->SetCurrentTauntMoveSpeed( clamp( m_pTFPlayer->GetCurrentTauntMoveSpeed() + flSign * ( gpGlobals->frametime / flAcceleration ) * flMaxMoveSpeed, 0.f, flMaxMoveSpeed ) );
+ }
+ else
+ {
+ m_pTFPlayer->SetCurrentTauntMoveSpeed( flMaxMoveSpeed );
+ }
+
+ // don't allow taunt to move if the player cannot move
+ if ( !m_pTFPlayer->CanPlayerMove() )
+ {
+ flMaxMoveSpeed = 0.f;
+ }
+
+ float flSmoothMoveSpeed = 0.f;
+ if ( flMaxMoveSpeed > 0.f )
+ {
+ flSmoothMoveSpeed = SimpleSpline( m_pTFPlayer->GetCurrentTauntMoveSpeed() / flMaxMoveSpeed ) * flMaxMoveSpeed;
+ }
+
+ mv->m_flMaxSpeed = flMaxMoveSpeed;
+ mv->m_flForwardMove = flMoveDir * flSmoothMoveSpeed;
+ mv->m_flClientMaxSpeed = flMaxMoveSpeed;
+
+ return true;
+ }
+ else
+ {
+ m_pTFPlayer->SetCurrentTauntMoveSpeed( 0.f );
+ }
+
+ return false;
+}
+
+ConVar tf_halloween_kart_dash_speed( "tf_halloween_kart_dash_speed", "1000", FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar tf_halloween_kart_dash_accel( "tf_halloween_kart_dash_accel", "750", FCVAR_CHEAT | FCVAR_REPLICATED );
+
+ConVar tf_halloween_kart_normal_speed( "tf_halloween_kart_normal_speed", "650", FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar tf_halloween_kart_normal_accel( "tf_halloween_kart_normal_accel", "300", FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar tf_halloween_kart_slowmoving_accel( "tf_halloween_kart_slowmoving_accel", "500", FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar tf_halloween_kart_slowmoving_threshold( "tf_halloween_kart_slowmoving_threshold", "300", FCVAR_CHEAT | FCVAR_REPLICATED );
+
+ConVar tf_halloween_kart_reverse_speed( "tf_halloween_kart_reverse_speed", "-50", FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar tf_halloween_kart_brake_speed( "tf_halloween_kart_brake_speed", "0", FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar tf_halloween_kart_brake_accel( "tf_halloween_kart_brake_accel", "500", FCVAR_CHEAT | FCVAR_REPLICATED );
+
+ConVar tf_halloween_kart_idle_speed( "tf_halloween_kart_idle_speed", "0", FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar tf_halloween_kart_coast_accel( "tf_halloween_kart_coast_accel", "300", FCVAR_CHEAT | FCVAR_REPLICATED );
+
+ConVar tf_halloween_kart_bombhead_scale( "tf_halloween_kart_bombhead_scale", "1.5f", FCVAR_CHEAT | FCVAR_REPLICATED );
+void CTFGameMovement::VehicleMove( void )
+{
+ // Reset Flags
+ m_pTFPlayer->m_iKartState = 0;
+ m_pTFPlayer->SetTauntYaw( mv->m_vecViewAngles[YAW] );
+
+ float flMaxMoveSpeed = tf_halloween_kart_normal_speed.GetFloat();
+
+ float flTargetSpeed = tf_halloween_kart_idle_speed.GetFloat();
+ // Just standard accell by default
+ float flAcceleration = tf_halloween_kart_coast_accel.GetFloat();
+
+ bool bInput = false;
+
+ // Hitting the gas
+ if ( mv->m_flForwardMove > 0.0f )
+ {
+ // Grab normalized analog input (no need to check key input explicitly, since it's already baked into m_flForwardMove
+ float flNormalizedForwardInput = cl_forwardspeed.GetFloat() > 0.0f ? mv->m_flForwardMove / cl_forwardspeed.GetFloat() : 0.0f;
+ if ( flNormalizedForwardInput > 1.0f )
+ {
+ flNormalizedForwardInput = 1.0f;
+ }
+
+ // Target normal speed
+ flTargetSpeed = tf_halloween_kart_normal_speed.GetFloat();
+ // Use normal accell speed if it's faster than our current speed
+ if ( flTargetSpeed > m_pTFPlayer->GetCurrentTauntMoveSpeed() )
+ {
+ if ( m_pTFPlayer->GetCurrentTauntMoveSpeed() < tf_halloween_kart_slowmoving_threshold.GetFloat() )
+ {
+ flAcceleration = tf_halloween_kart_slowmoving_accel.GetFloat() * flNormalizedForwardInput;
+ }
+ else
+ {
+ flAcceleration = tf_halloween_kart_normal_accel.GetFloat() * flNormalizedForwardInput;
+ }
+ }
+
+ bInput = true;
+ m_pTFPlayer->m_iKartState |= CTFPlayerShared::kKartState_Driving;
+ }
+ else if ( mv->m_flForwardMove < 0.0f ) // Hitting the brakes
+ {
+ // Grab normalized analog input (no need to check key input explicitly, since it's already baked into m_flForwardMove. And flip the sign, since we're going backwards.
+ float flNormalizedForwardInput = cl_backspeed.GetFloat() > 0.0f ? mv->m_flForwardMove / cl_backspeed.GetFloat() : 0.0f;
+ if ( flNormalizedForwardInput < -1.0f )
+ {
+ flNormalizedForwardInput = 1.0f;
+ }
+ else
+ {
+ flNormalizedForwardInput = -flNormalizedForwardInput;
+ }
+
+ // slowing down
+ if ( m_pTFPlayer->GetCurrentTauntMoveSpeed() > 0 )
+ {
+ // Target brake speed
+ flTargetSpeed = tf_halloween_kart_brake_speed.GetFloat();
+ // Use brake accell speed if it's slower than our current speed
+ if ( flTargetSpeed < m_pTFPlayer->GetCurrentTauntMoveSpeed() )
+ {
+ flAcceleration = tf_halloween_kart_brake_accel.GetFloat() * flNormalizedForwardInput;
+ }
+ m_pTFPlayer->m_iKartState |= CTFPlayerShared::kKartState_Braking;
+ }
+ // if we are already stopped, look for new input to start going backwards
+ else
+ {
+ // check for new input, else do nothing
+ if ( mv->m_flOldForwardMove >= 0.0f || m_pTFPlayer->GetCurrentTauntMoveSpeed() < 0 || m_pTFPlayer->GetVehicleReverseTime() < gpGlobals->curtime )
+ {
+ // going backwards, keep going backwards
+ flTargetSpeed = tf_halloween_kart_reverse_speed.GetFloat();
+ // Use brake accell speed if it's slower than our current speed
+ if ( flTargetSpeed < m_pTFPlayer->GetCurrentTauntMoveSpeed() )
+ {
+ flAcceleration = tf_halloween_kart_brake_accel.GetFloat() * flNormalizedForwardInput;
+ }
+ m_pTFPlayer->m_iKartState |= CTFPlayerShared::kKartState_Reversing;
+ }
+ else
+ {
+ // Stall for 1 second then start reversing
+ if ( m_pTFPlayer->GetVehicleReverseTime() == FLT_MAX )
+ {
+ m_pTFPlayer->SetVehicleReverseTime( gpGlobals->curtime + 0.6f );
+ }
+ m_pTFPlayer->m_iKartState |= CTFPlayerShared::kKartState_Stopped;
+ }
+ }
+
+ bInput = true;
+ }
+
+ if ( m_pTFPlayer->GetCurrentTauntMoveSpeed() > 0 )
+ {
+ m_pTFPlayer->SetVehicleReverseTime( FLT_MAX );
+ }
+
+ // braking?
+ if ( bInput && Sign( m_pTFPlayer->GetCurrentTauntMoveSpeed() ) != Sign( flTargetSpeed ) )
+ {
+ flAcceleration = tf_halloween_kart_brake_accel.GetFloat();
+ }
+
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) )
+ {
+ flMaxMoveSpeed *= tf_halloween_kart_bombhead_scale.GetFloat();
+ flAcceleration *= tf_halloween_kart_bombhead_scale.GetFloat();
+ }
+
+ float flTargetMoveSpeed = Approach( flTargetSpeed, m_pTFPlayer->GetCurrentTauntMoveSpeed(), flAcceleration * gpGlobals->frametime );
+ float flSmoothMoveSpeed = Bias( fabs( m_pTFPlayer->GetCurrentTauntMoveSpeed() ) / flMaxMoveSpeed, 0.7f ) * flMaxMoveSpeed * Sign( flTargetMoveSpeed );
+
+ // Boost slams the accelerator
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART_DASH ) )
+ {
+ flTargetSpeed = tf_halloween_kart_dash_speed.GetFloat();
+ flMaxMoveSpeed = tf_halloween_kart_dash_speed.GetFloat();
+ flTargetMoveSpeed = flTargetSpeed;
+ flSmoothMoveSpeed = flTargetSpeed;
+ flAcceleration = tf_halloween_kart_dash_accel.GetFloat();
+ }
+
+ m_pTFPlayer->SetCurrentTauntMoveSpeed( flTargetMoveSpeed );
+ float flLeanAccel = flTargetSpeed > flSmoothMoveSpeed ? flAcceleration : flTargetSpeed < flSmoothMoveSpeed ? -flAcceleration : 0.f;
+ flLeanAccel = Sign( m_pTFPlayer->GetCurrentTauntMoveSpeed() ) != Sign( flTargetSpeed ) ? -flLeanAccel : flLeanAccel;
+ m_pTFPlayer->m_PlayerAnimState->Vehicle_LeanAccel( flLeanAccel );
+
+#ifdef DEBUG
+ engine->Con_NPrintf( 0, "Speed: %3.2f", m_pTFPlayer->GetCurrentTauntMoveSpeed() );
+ engine->Con_NPrintf( 1, "Target: %3.2f", flTargetSpeed );
+ engine->Con_NPrintf( 2, "Accell: %3.2f", flAcceleration );
+#endif
+
+ mv->m_flMaxSpeed = flMaxMoveSpeed;
+ mv->m_flForwardMove = flSmoothMoveSpeed;
+ mv->m_flClientMaxSpeed = flMaxMoveSpeed;
+ mv->m_flSideMove = 0.f; // No sideways movement
+}
+
+
+bool CTFGameMovement::HighMaxSpeedMove()
+{
+ if ( fabsf( mv->m_flForwardMove ) < player->MaxSpeed() )
+ {
+ if ( AlmostEqual( mv->m_flForwardMove, cl_forwardspeed.GetFloat() ) )
+ {
+ mv->m_flForwardMove = player->MaxSpeed();
+ }
+ else if ( AlmostEqual( mv->m_flForwardMove, -cl_backspeed.GetFloat() ) )
+ {
+ mv->m_flForwardMove = -player->MaxSpeed();
+ }
+ }
+
+ if ( fabsf( mv->m_flSideMove ) < player->MaxSpeed() )
+ {
+ if ( AlmostEqual( mv->m_flSideMove, cl_sidespeed.GetFloat() ) )
+ {
+ mv->m_flSideMove = player->MaxSpeed();
+ }
+ else if ( AlmostEqual( mv->m_flSideMove, -cl_sidespeed.GetFloat() ) )
+ {
+ mv->m_flSideMove = -player->MaxSpeed();
+ }
+ }
+
+ return true;
+}
+
+bool CTFGameMovement::CanAccelerate()
+{
+ // Only allow the player to accelerate when in certain states.
+ int nCurrentState = m_pTFPlayer->m_Shared.GetState();
+ if ( nCurrentState == TF_STATE_ACTIVE )
+ {
+ return player->GetWaterJumpTime() == 0;
+ }
+ else if ( player->IsObserver() )
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check to see if we are in water. If so the jump button acts like a
+// swim upward key.
+//-----------------------------------------------------------------------------
+bool CTFGameMovement::CheckWaterJumpButton( void )
+{
+ // See if we are water jumping. If so, decrement count and return.
+ if ( player->m_flWaterJumpTime )
+ {
+ player->m_flWaterJumpTime -= gpGlobals->frametime;
+ if (player->m_flWaterJumpTime < 0)
+ {
+ player->m_flWaterJumpTime = 0;
+ }
+
+ return false;
+ }
+
+ // In water above our waist.
+ if ( player->GetWaterLevel() >= 2 || m_pTFPlayer->m_Shared.InCond( TF_COND_SWIMMING_NO_EFFECTS ) )
+ {
+ // Swimming, not jumping.
+ SetGroundEntity( NULL );
+
+ int iCannotSwim = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pTFPlayer, iCannotSwim, cannot_swim );
+ if ( iCannotSwim )
+ {
+ return false;
+ }
+
+ // We move up a certain amount.
+ if ( player->GetWaterType() == CONTENTS_WATER || m_pTFPlayer->m_Shared.InCond( TF_COND_SWIMMING_NO_EFFECTS ) )
+ {
+ mv->m_vecVelocity[2] = 100;
+ }
+ else if ( player->GetWaterType() == CONTENTS_SLIME )
+ {
+ mv->m_vecVelocity[2] = 80;
+ }
+
+ // Play swimming sound.
+ if ( player->m_flSwimSoundTime <= 0 && !m_pTFPlayer->m_Shared.InCond( TF_COND_SWIMMING_NO_EFFECTS ) )
+ {
+ // Don't play sound again for 1 second.
+ player->m_flSwimSoundTime = 1000;
+ PlaySwimSound();
+ }
+
+ return false;
+ }
+
+ return true;
+}
+
+void CTFGameMovement::AirDash( void )
+{
+ // Apply approx. the jump velocity added to an air dash.
+ Assert( GetCurrentGravity() == 800.0f );
+
+ float flJumpMod = 1.f;
+ // Passive version
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pTFPlayer, flJumpMod, mod_jump_height );
+ // Weapon-restricted version
+ CTFWeaponBase *pWpn = m_pTFPlayer->GetActiveTFWeapon();
+ if ( pWpn )
+ {
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWpn, flJumpMod, mod_jump_height_from_weapon );
+ }
+
+ // Lose hype on airdash
+ int iHypeResetsOnJump = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pTFPlayer, iHypeResetsOnJump, hype_resets_on_jump );
+ if ( iHypeResetsOnJump != 0 )
+ {
+ // Loose x hype on jump
+ float flHype = m_pTFPlayer->m_Shared.GetScoutHypeMeter();
+ m_pTFPlayer->m_Shared.SetScoutHypeMeter( flHype - iHypeResetsOnJump );
+ m_pTFPlayer->TeamFortress_SetSpeed();
+ }
+
+ if ( m_pTFPlayer->m_Shared.GetCarryingRuneType() == RUNE_AGILITY )
+ {
+ flJumpMod *= 1.8f;
+ }
+
+ float flDashZ = 268.3281572999747f * flJumpMod;
+
+ // Get the wish direction.
+ Vector vecForward, vecRight;
+ AngleVectors( mv->m_vecViewAngles, &vecForward, &vecRight, NULL );
+ vecForward.z = 0.0f;
+ vecRight.z = 0.0f;
+ VectorNormalize( vecForward );
+ VectorNormalize( vecRight );
+
+ // Copy movement amounts
+ float flForwardMove = mv->m_flForwardMove;
+ float flSideMove = mv->m_flSideMove;
+
+ // Find the direction,velocity in the x,y plane.
+ Vector vecWishDirection( ( ( vecForward.x * flForwardMove ) + ( vecRight.x * flSideMove ) ),
+ ( ( vecForward.y * flForwardMove ) + ( vecRight.y * flSideMove ) ),
+ 0.0f );
+
+ // Update the velocity on the scout.
+ mv->m_vecVelocity = vecWishDirection;
+ mv->m_vecVelocity.z += flDashZ;
+
+ int iAirDash = m_pTFPlayer->m_Shared.GetAirDash();
+ if ( iAirDash == 0 )
+ {
+#if defined(GAME_DLL)
+ // Our first air jump.
+ m_pTFPlayer->SpeakConceptIfAllowed( MP_CONCEPT_DOUBLE_JUMP, "started_jumping:1" );
+#else
+ IGameEvent *event = gameeventmanager->CreateEvent( "air_dash" );
+ if ( event )
+ {
+ event->SetInt( "player", m_pTFPlayer->GetUserID() );
+ gameeventmanager->FireEventClientSide( event );
+ }
+#endif
+ }
+ else
+ {
+#ifdef GAME_DLL
+ // Exertion damage from multi-dashing ( atomizer )
+ if ( !m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_SPEED_BOOST ) && !m_pTFPlayer->m_Shared.InCond( TF_COND_SODAPOPPER_HYPE ) )
+ {
+ m_pTFPlayer->TakeDamage( CTakeDamageInfo( m_pTFPlayer, m_pTFPlayer, vec3_origin, m_pTFPlayer->WorldSpaceCenter( ), 10.f, DMG_BULLET ) );
+ }
+#endif
+ }
+ m_pTFPlayer->m_Shared.SetAirDash( iAirDash+1 );
+
+ // Play the gesture.
+ m_pTFPlayer->DoAnimationEvent( PLAYERANIMEVENT_DOUBLEJUMP );
+#ifdef GAME_DLL
+ // Pitch shift a sound for all airdashes greater then 1
+ if ( iAirDash > 0 )
+ {
+ EmitSound_t params;
+ params.m_pSoundName = "General.banana_slip";
+ params.m_flSoundTime = 0;
+ params.m_pflSoundDuration = 0;
+ //params.m_bWarnOnDirectWaveReference = true;
+ CPASFilter filter( m_pTFPlayer->GetAbsOrigin( ) );
+ params.m_flVolume = 0.1f;
+ params.m_SoundLevel = SNDLVL_25dB;
+ params.m_nPitch = RemapVal( iAirDash, 1.0f, 5.0f, 100.f, 120.f );
+ params.m_nFlags |= ( SND_CHANGE_PITCH | SND_CHANGE_VOL );
+ m_pTFPlayer->StopSound( "General.banana_slip" );
+ m_pTFPlayer->EmitSound( filter, m_pTFPlayer->entindex( ), params );
+ }
+#endif // GAME_DLL
+}
+
+// Only allow bunny jumping up to 1.2x server / player maxspeed setting
+#define BUNNYJUMP_MAX_SPEED_FACTOR 1.2f
+
+void CTFGameMovement::PreventBunnyJumping()
+{
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
+ return;
+
+ // Speed at which bunny jumping is limited
+ float maxscaledspeed = BUNNYJUMP_MAX_SPEED_FACTOR * player->m_flMaxspeed;
+ if ( maxscaledspeed <= 0.0f )
+ return;
+
+ // Current player speed
+ float spd = mv->m_vecVelocity.Length();
+ if ( spd <= maxscaledspeed )
+ return;
+
+ // Apply this cropping fraction to velocity
+ float fraction = ( maxscaledspeed / spd );
+
+
+ mv->m_vecVelocity *= fraction;
+}
+
+void CTFGameMovement::ToggleParachute()
+{
+ if ( (m_pTFPlayer->GetFlags() & FL_ONGROUND) || (mv->m_nOldButtons & IN_JUMP) )
+ return;
+
+ // Can not add if in kart (Kart code does it for spell) but players can manually undeploy
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
+ {
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_PARACHUTE_DEPLOYED ) )
+ {
+ m_pTFPlayer->m_Shared.RemoveCond( TF_COND_PARACHUTE_DEPLOYED );
+ }
+ return;
+ }
+
+ // Check for Parachute and deploy / undeploy
+ int iParachute = 0;
+ // Passive version
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pTFPlayer, iParachute, parachute_attribute );
+ if ( iParachute )
+ {
+ // Toggle between the conditions
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_PARACHUTE_DEPLOYED ) )
+ {
+ m_pTFPlayer->m_Shared.RemoveCond( TF_COND_PARACHUTE_DEPLOYED );
+ }
+ else
+ {
+ int iParachuteDisabled = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pTFPlayer, iParachuteDisabled, parachute_disabled );
+ if ( !iParachuteDisabled )
+ {
+ m_pTFPlayer->m_Shared.AddCond( TF_COND_PARACHUTE_DEPLOYED );
+ }
+ }
+ }
+}
+
+bool CTFGameMovement::CheckJumpButton()
+{
+ // Are we dead? Then we cannot jump.
+ if ( player->pl.deadflag )
+ return false;
+
+ // Check to see if we are in water.
+ if ( !CheckWaterJumpButton() )
+ return false;
+
+ if ( m_pTFPlayer->GetGrapplingHookTarget() )
+ {
+ float flStartZ = mv->m_vecVelocity[2];
+ mv->m_vecVelocity[2] += tf_grapplinghook_jump_up_speed.GetFloat();
+
+ // Heavy gets a jump height reduction across the board, even if he has Agility
+ // Powered up flag carriers get the same penalty
+ if ( m_pTFPlayer->GetPlayerClass()->GetClassIndex() == TF_CLASS_HEAVYWEAPONS || ( m_pTFPlayer->m_Shared.GetCarryingRuneType() != RUNE_NONE && m_pTFPlayer->HasTheFlag() ) )
+ {
+ mv->m_vecVelocity[2] *= 0.80f;
+ }
+ else if ( m_pTFPlayer->m_Shared.GetCarryingRuneType() != RUNE_AGILITY && m_pTFPlayer->m_Shared.GetCarryingRuneType() != RUNE_NONE && m_pTFPlayer->HasTheFlag() )
+ {
+ mv->m_vecVelocity[2] *= 0.80f;
+ }
+
+ if ( mv->m_vecVelocity[2] > GetAirSpeedCap() )
+ mv->m_vecVelocity[2] = GetAirSpeedCap();
+
+ // Apply gravity.
+ FinishGravity();
+
+ mv->m_outJumpVel.z = mv->m_vecVelocity[2] - flStartZ;
+ mv->m_outStepHeight += 0.15f;
+ mv->m_nOldButtons |= IN_JUMP;
+
+ return true;
+ }
+
+ // holding jump key will make ghost fly
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
+ {
+ float flStartZ = mv->m_vecVelocity[2];
+ mv->m_vecVelocity[2] = tf_ghost_up_speed.GetFloat();
+
+ // Apply gravity.
+ FinishGravity();
+
+ mv->m_outJumpVel.z = mv->m_vecVelocity[2] - flStartZ;
+ mv->m_outStepHeight += 0.15f;
+ mv->m_nOldButtons |= IN_JUMP;
+ return true;
+ }
+
+ // Can't jump if our weapon disallows it.
+ CTFWeaponBase *pWpn = m_pTFPlayer->GetActiveTFWeapon();
+ if ( pWpn && !pWpn->OwnerCanJump() )
+ return false;
+
+ // Cannot jump while taunting
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_TAUNTING ) )
+ return false;
+
+ // Check to see if the player is a scout.
+ bool bScout = m_pTFPlayer->GetPlayerClass()->IsClass( TF_CLASS_SCOUT );
+ bool bAirDash = false;
+ bool bOnGround = ( player->GetGroundEntity() != NULL );
+
+ ToggleParachute();
+
+ // Cannot jump will ducked.
+ if ( player->GetFlags() & FL_DUCKING )
+ {
+ // Let a scout do it.
+ bool bAllow = ( bScout && !bOnGround );
+
+ if ( !bAllow )
+ return false;
+ }
+
+ // Cannot jump while in the unduck transition.
+ if ( ( player->m_Local.m_bDucking && ( player->GetFlags() & FL_DUCKING ) ) || ( player->m_Local.m_flDuckJumpTime > 0.0f ) )
+ return false;
+
+ // Cannot jump again until the jump button has been released.
+ if ( mv->m_nOldButtons & IN_JUMP )
+ return false;
+
+ // In air, so ignore jumps
+ // (unless you are a scout or ghost or parachute
+ if ( !bOnGround )
+ {
+ if ( m_pTFPlayer->CanAirDash() )
+ {
+ bAirDash = true;
+ }
+ else
+ {
+ mv->m_nOldButtons |= IN_JUMP;
+ return false;
+ }
+ }
+
+ // Check for an air dash.
+ if ( bAirDash )
+ {
+ AirDash();
+ // Reset air duck for Scouts on AirDash.
+ m_pTFPlayer->m_Shared.SetAirDucked( 0 );
+ return true;
+ }
+
+ PreventBunnyJumping();
+
+ // Start jump animation and player sound (specific TF animation and flags).
+ m_pTFPlayer->DoAnimationEvent( PLAYERANIMEVENT_JUMP );
+ player->PlayStepSound( (Vector &)mv->GetAbsOrigin(), player->m_pSurfaceData, 1.0, true );
+ m_pTFPlayer->m_Shared.SetJumping( true );
+
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
+ {
+ m_pTFPlayer->EmitSound( "BumperCar.Jump" );
+ }
+
+ // Set the player as in the air.
+ SetGroundEntity( NULL );
+
+ // Check the surface the player is standing on to see if it impacts jumping.
+ float flGroundFactor = 1.0f;
+ if ( player->m_pSurfaceData )
+ {
+ flGroundFactor = player->m_pSurfaceData->game.jumpFactor;
+ }
+
+ // fMul = sqrt( 2.0 * gravity * jump_height (21.0units) ) * GroundFactor
+ Assert( GetCurrentGravity() == 800.0f );
+
+ float flJumpMod = 1.f;
+ //if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
+ //{
+ // flJumpMod *= 1.3f;
+ //}
+
+ // Passive version
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pTFPlayer, flJumpMod, mod_jump_height );
+ // Weapon-restricted version
+ if ( pWpn )
+ {
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWpn, flJumpMod, mod_jump_height_from_weapon );
+ }
+/*
+#ifdef STAGING_ONLY
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_SPACE_GRAVITY ) )
+ {
+ flJumpMod *= tf_space_gravity_jump_multipler.GetFloat();
+ }
+
+#endif // STAGING_ONLY
+*/
+ if ( m_pTFPlayer->m_Shared.GetCarryingRuneType() == RUNE_AGILITY )
+ {
+ flJumpMod *= 1.8f;
+ }
+
+ float flMul = ( 289.0f * flJumpMod ) * flGroundFactor;
+
+ // Save the current z velocity.
+ float flStartZ = mv->m_vecVelocity[2];
+
+ // Acclerate upward
+ if ( ( player->m_Local.m_bDucking ) || ( player->GetFlags() & FL_DUCKING ) )
+ {
+ // If we are ducking...
+ // d = 0.5 * g * t^2 - distance traveled with linear accel
+ // t = sqrt(2.0 * 45 / g) - how long to fall 45 units
+ // v = g * t - velocity at the end (just invert it to jump up that high)
+ // v = g * sqrt(2.0 * 45 / g )
+ // v^2 = g * g * 2.0 * 45 / g
+ // v = sqrt( g * 2.0 * 45 )
+ mv->m_vecVelocity[2] = flMul; // 2 * gravity * jump_height * ground_factor
+ }
+ else
+ {
+ mv->m_vecVelocity[2] += flMul; // 2 * gravity * jump_height * ground_factor
+ }
+
+ // Apply gravity.
+ FinishGravity();
+
+ // Save the output data for the physics system to react to if need be.
+ mv->m_outJumpVel.z += mv->m_vecVelocity[2] - flStartZ;
+ mv->m_outStepHeight += 0.15f;
+
+ // Flag that we jumped and don't jump again until it is released.
+ mv->m_nOldButtons |= IN_JUMP;
+ return true;
+}
+
+//--------------------------------------------------------
+int CTFGameMovement::CheckStuck( void )
+{
+ // assume we are not stuck in a player
+ m_isPassingThroughEnemies = false;
+
+ if ( tf_resolve_stuck_players.GetBool() )
+ {
+ const Vector &originalPos = mv->GetAbsOrigin();
+ trace_t traceresult;
+
+ TracePlayerBBox( originalPos, originalPos, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, traceresult );
+
+#ifdef GAME_DLL
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && m_pTFPlayer && m_pTFPlayer->GetTeamNumber() == TF_TEAM_PVE_INVADERS )
+ {
+ if ( traceresult.startsolid )
+ {
+ if ( m_pTFPlayer->m_playerMovementStuckTimer.HasStarted() && m_pTFPlayer->m_playerMovementStuckTimer.IsElapsed() )
+ {
+ DevMsg( "%3.2f: A robot is interpenetrating a solid - killed!\n", gpGlobals->curtime );
+
+ UTIL_LogPrintf( "\"%s<%i><%s><%s>\" startsolid killed (position \"%3.2f %3.2f %3.2f\")\n",
+ m_pTFPlayer->GetPlayerName(),
+ m_pTFPlayer->GetUserID(),
+ m_pTFPlayer->GetNetworkIDString(),
+ m_pTFPlayer->GetTeam()->GetName(),
+ m_pTFPlayer->GetAbsOrigin().x, m_pTFPlayer->GetAbsOrigin().y, m_pTFPlayer->GetAbsOrigin().z );
+
+ m_pTFPlayer->TakeDamage( CTakeDamageInfo( m_pTFPlayer, m_pTFPlayer, vec3_origin, m_pTFPlayer->WorldSpaceCenter(), 999999.9f, DMG_CRUSH ) );
+ }
+ else
+ {
+ if ( traceresult.m_pEnt )
+ {
+ Warning( "Robot's getting stuck with %s\n", traceresult.m_pEnt->GetClassname() );
+ }
+ }
+ }
+ else
+ {
+ // Bot is *not* stuck right now. Continually restart timer, so if we become stuck it will count down and expire.
+ const float stuckTooLongTime = 10.0f;
+ m_pTFPlayer->m_playerMovementStuckTimer.Start( stuckTooLongTime );
+ }
+ }
+#endif
+
+ if ( traceresult.startsolid && traceresult.DidHitNonWorldEntity() )
+ {
+ if ( traceresult.m_pEnt->IsPlayer() )
+ {
+ // We are stuck in an enemy player. Don't collide with enemies until we are no longer penetrating them.
+ m_isPassingThroughEnemies = true;
+
+ // verify position is now clear
+ TracePlayerBBox( originalPos, originalPos, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, traceresult );
+
+ if ( !traceresult.DidHit() )
+ {
+ // no longer stuck
+ DevMsg( "%3.2f: Resolved stuck player/player\n", gpGlobals->curtime );
+
+ return 0;
+ }
+ }
+ else if ( fabs( traceresult.m_pEnt->GetAbsVelocity().z ) > 0.7071f && FClassnameIs( traceresult.m_pEnt, "func_tracktrain" ) )
+ {
+ // we're stuck in a vertically moving tracktrain, assume flat surface normal and move us out
+ SetGroundEntity( &traceresult );
+
+ // we're stuck in a vertically moving tracktrain, snap on top of it
+ const float maxAdjust = 80.0f;
+ const float step = 10.0f;
+ Vector tryPos;
+ for( float shift = step; shift < maxAdjust; shift += step )
+ {
+ tryPos = mv->GetAbsOrigin();
+ tryPos.z += shift;
+
+ TracePlayerBBox( tryPos, tryPos, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, traceresult );
+ if ( !traceresult.DidHit() || ( traceresult.m_pEnt && traceresult.m_pEnt->IsPlayer() ) )
+ {
+ // no longer stuck
+ mv->SetAbsOrigin( tryPos );
+
+ DevMsg( "%3.2f: Forced stuck player to top of func_tracktrain\n", gpGlobals->curtime );
+
+ return 0;
+ }
+ }
+ }
+ }
+ }
+
+ return BaseClass::CheckStuck();
+}
+
+
+bool CTFGameMovement::CheckWater( void )
+{
+ Vector vecPlayerMin = GetPlayerMins();
+ Vector vecPlayerMax = GetPlayerMaxs();
+
+ Vector vecPoint( ( mv->GetAbsOrigin().x + ( vecPlayerMin.x + vecPlayerMax.x ) * 0.5f ),
+ ( mv->GetAbsOrigin().y + ( vecPlayerMin.y + vecPlayerMax.y ) * 0.5f ),
+ ( mv->GetAbsOrigin().z + vecPlayerMin.z + 1 ) );
+
+
+ // Assume that we are not in water at all.
+ int wl = WL_NotInWater;
+ int wt = CONTENTS_EMPTY;
+
+ // Check to see if our feet are underwater.
+ int nContents = GetPointContentsCached( vecPoint, 0 );
+ if ( nContents & MASK_WATER )
+ {
+ // Clear our jump flag, because we have landed in water.
+ m_pTFPlayer->m_Shared.SetJumping( false );
+
+ // Set water type and level.
+ wt = nContents;
+ wl = WL_Feet;
+
+ float flWaistZ = mv->GetAbsOrigin().z + ( vecPlayerMin.z + vecPlayerMax.z ) * 0.5f + 12.0f;
+
+ // Now check eyes
+ vecPoint.z = mv->GetAbsOrigin().z + player->GetViewOffset()[2];
+ nContents = GetPointContentsCached( vecPoint, 1 );
+ if ( nContents & MASK_WATER )
+ {
+ // In over our eyes
+ wl = WL_Eyes;
+ VectorCopy( vecPoint, m_vecWaterPoint );
+ m_vecWaterPoint.z = flWaistZ;
+ }
+ else
+ {
+ // Now check a point that is at the player hull midpoint (waist) and see if that is underwater.
+ vecPoint.z = flWaistZ;
+ nContents = GetPointContentsCached( vecPoint, 2 );
+ if ( nContents & MASK_WATER )
+ {
+ // Set the water level at our waist.
+ wl = WL_Waist;
+ VectorCopy( vecPoint, m_vecWaterPoint );
+ }
+ }
+ }
+
+ // force player to be under water
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_SWIMMING_CURSE ) )
+ {
+ wl = WL_Eyes;
+ }
+
+ player->SetWaterLevel( wl );
+ player->SetWaterType( wt );
+
+ // If we just transitioned from not in water to water, record the time for splashes, etc.
+ if ( ( WL_NotInWater == m_nOldWaterLevel ) && ( wl > WL_NotInWater ) )
+ {
+ m_flWaterEntryTime = gpGlobals->curtime;
+ }
+#ifdef GAME_DLL
+ else if ( ( WL_NotInWater == wl ) && ( m_nOldWaterLevel > WL_NotInWater ) )
+ {
+ m_pTFPlayer->SetWaterExitTime( gpGlobals->curtime );
+ }
+#endif
+
+ if ( m_nOldWaterLevel != wl )
+ {
+ m_pTFPlayer->TeamFortress_SetSpeed();
+ }
+
+ return ( wl > WL_Feet );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGameMovement::WaterMove( void )
+{
+ float wishspeed;
+ Vector wishdir;
+ Vector start, dest;
+ Vector temp;
+ trace_t pm;
+ float speed, newspeed, addspeed, accelspeed;
+
+ // Determine movement angles.
+ Vector vecForward, vecRight, vecUp;
+ AngleVectors( mv->m_vecViewAngles, &vecForward, &vecRight, &vecUp );
+
+ // Calculate the desired direction and speed.
+ Vector vecWishVelocity;
+ for ( int iAxis = 0 ; iAxis < 3; ++iAxis )
+ {
+ vecWishVelocity[iAxis] = ( vecForward[iAxis] * mv->m_flForwardMove ) + ( vecRight[iAxis] * mv->m_flSideMove );
+ }
+
+ // if you can't swim just sink instead
+ int iCannotSwim = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pTFPlayer, iCannotSwim, cannot_swim );
+ if ( iCannotSwim )
+ {
+ vecWishVelocity[0] *= 0.1;
+ vecWishVelocity[1] *= 0.1;
+ vecWishVelocity[2] = -60;
+ }
+ // Check for upward velocity (JUMP).
+ else if ( mv->m_nButtons & IN_JUMP )
+ {
+ if ( player->GetWaterLevel() == WL_Eyes )
+ {
+ vecWishVelocity[2] += mv->m_flClientMaxSpeed;
+ }
+ }
+ // Sinking if not moving.
+ else if ( !mv->m_flForwardMove && !mv->m_flSideMove && !mv->m_flUpMove )
+ {
+ vecWishVelocity[2] -= 60;
+ }
+ // Move up based on view angle.
+ else
+ {
+ vecWishVelocity[2] += mv->m_flUpMove;
+ }
+
+ // Copy it over and determine speed
+ VectorCopy( vecWishVelocity, wishdir );
+ wishspeed = VectorNormalize( wishdir );
+
+ // Cap speed.
+ if (wishspeed > mv->m_flMaxSpeed)
+ {
+ VectorScale( vecWishVelocity, mv->m_flMaxSpeed/wishspeed, vecWishVelocity );
+ wishspeed = mv->m_flMaxSpeed;
+ }
+
+ // Slow us down a bit.
+ int iSwimmingMastery = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pTFPlayer, iSwimmingMastery, swimming_mastery );
+ if ( iSwimmingMastery == 0 )
+ {
+ wishspeed *= 0.8;
+ }
+
+ // Water friction
+ VectorCopy( mv->m_vecVelocity, temp );
+ speed = VectorNormalize( temp );
+ if ( speed )
+ {
+ newspeed = speed - gpGlobals->frametime * speed * sv_friction.GetFloat() * player->m_surfaceFriction;
+ if ( newspeed < 0.1f )
+ {
+ newspeed = 0;
+ }
+
+ VectorScale (mv->m_vecVelocity, newspeed/speed, mv->m_vecVelocity);
+ }
+ else
+ {
+ newspeed = 0;
+ }
+
+ // water acceleration
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
+ {
+ VectorNormalize(vecWishVelocity);
+ accelspeed = sv_accelerate.GetFloat() * wishspeed * gpGlobals->frametime * player->m_surfaceFriction;
+ for ( int i = 0; i < 3; i++)
+ {
+ float deltaSpeed = accelspeed * vecWishVelocity[i];
+ mv->m_vecVelocity[i] += deltaSpeed;
+ mv->m_outWishVel[i] += deltaSpeed;
+ }
+
+ float flGhostXYSpeed = mv->m_vecVelocity.Length2D();
+ if ( flGhostXYSpeed > tf_ghost_xy_speed.GetFloat() )
+ {
+ float flGhostXYSpeedScale = tf_ghost_xy_speed.GetFloat() / flGhostXYSpeed;
+ mv->m_vecVelocity.x *= flGhostXYSpeedScale;
+ mv->m_vecVelocity.y *= flGhostXYSpeedScale;
+ }
+ }
+ else if (wishspeed >= 0.1f) // old !
+ {
+ addspeed = wishspeed - newspeed;
+ if (addspeed > 0)
+ {
+ VectorNormalize(vecWishVelocity);
+ accelspeed = sv_accelerate.GetFloat() * wishspeed * gpGlobals->frametime * player->m_surfaceFriction;
+ if (accelspeed > addspeed)
+ {
+ accelspeed = addspeed;
+ }
+
+ for ( int i = 0; i < 3; i++)
+ {
+ float deltaSpeed = accelspeed * vecWishVelocity[i];
+ mv->m_vecVelocity[i] += deltaSpeed;
+ mv->m_outWishVel[i] += deltaSpeed;
+ }
+ }
+ }
+
+ VectorAdd (mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity);
+
+ // Now move
+ // assume it is a stair or a slope, so press down from stepheight above
+ VectorMA (mv->GetAbsOrigin(), gpGlobals->frametime, mv->m_vecVelocity, dest);
+
+ TracePlayerBBox( mv->GetAbsOrigin(), dest, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, pm );
+ if ( pm.fraction == 1.0f )
+ {
+ VectorCopy( dest, start );
+ if ( player->m_Local.m_bAllowAutoMovement )
+ {
+ start[2] += player->m_Local.m_flStepSize + 1;
+ }
+
+ TracePlayerBBox( start, dest, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, pm );
+
+ if (!pm.startsolid && !pm.allsolid)
+ {
+#if 0
+ float stepDist = pm.endpos.z - mv->GetAbsOrigin().z;
+ mv->m_outStepHeight += stepDist;
+ // walked up the step, so just keep result and exit
+
+ Vector vecNewWaterPoint;
+ VectorCopy( m_vecWaterPoint, vecNewWaterPoint );
+ vecNewWaterPoint.z += ( dest.z - mv->GetAbsOrigin().z );
+ bool bOutOfWater = !( enginetrace->GetPointContents( vecNewWaterPoint ) & MASK_WATER );
+ if ( bOutOfWater && ( mv->m_vecVelocity.z > 0.0f ) && ( pm.fraction == 1.0f ) )
+ {
+ // Check the waist level water positions.
+ trace_t traceWater;
+ UTIL_TraceLine( vecNewWaterPoint, m_vecWaterPoint, CONTENTS_WATER, player, COLLISION_GROUP_NONE, &traceWater );
+ if( traceWater.fraction < 1.0f )
+ {
+ float flFraction = 1.0f - traceWater.fraction;
+
+// Vector vecSegment;
+// VectorSubtract( mv->GetAbsOrigin(), dest, vecSegment );
+// VectorMA( mv->GetAbsOrigin(), flFraction, vecSegment, mv->GetAbsOrigin() );
+ float flZDiff = dest.z - mv->GetAbsOrigin().z;
+ float flSetZ = mv->GetAbsOrigin().z + ( flFraction * flZDiff );
+ flSetZ -= 0.0325f;
+
+ VectorCopy (pm.endpos, mv->GetAbsOrigin());
+ mv->GetAbsOrigin().z = flSetZ;
+ VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
+ mv->m_vecVelocity.z = 0.0f;
+ }
+
+ }
+ else
+ {
+ VectorCopy (pm.endpos, mv->GetAbsOrigin());
+ VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
+ }
+
+ return;
+#endif
+ float stepDist = pm.endpos.z - mv->GetAbsOrigin().z;
+ mv->m_outStepHeight += stepDist;
+ // walked up the step, so just keep result and exit
+ mv->SetAbsOrigin( pm.endpos );
+ VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
+ return;
+ }
+
+ // Try moving straight along out normal path.
+ TryPlayerMove();
+ }
+ else
+ {
+ if ( !player->GetGroundEntity() )
+ {
+ TryPlayerMove();
+ VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
+ return;
+ }
+
+ StepMove( dest, pm );
+ }
+
+ VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGameMovement::WalkMove( void )
+{
+ // Get the movement angles.
+ Vector vecForward, vecRight, vecUp;
+ AngleVectors( mv->m_vecViewAngles, &vecForward, &vecRight, &vecUp );
+ vecForward.z = 0.0f;
+ vecRight.z = 0.0f;
+ VectorNormalize( vecForward );
+ VectorNormalize( vecRight );
+
+ // Copy movement amounts
+ float flForwardMove = mv->m_flForwardMove;
+ float flSideMove = mv->m_flSideMove;
+
+ // Find the direction,velocity in the x,y plane.
+ Vector vecWishDirection( ( ( vecForward.x * flForwardMove ) + ( vecRight.x * flSideMove ) ),
+ ( ( vecForward.y * flForwardMove ) + ( vecRight.y * flSideMove ) ),
+ 0.0f );
+
+ // Calculate the speed and direction of movement, then clamp the speed.
+ float flWishSpeed = VectorNormalize( vecWishDirection );
+ flWishSpeed = clamp( flWishSpeed, 0.0f, mv->m_flMaxSpeed );
+
+ // Accelerate in the x,y plane.
+ mv->m_vecVelocity.z = 0;
+
+ float flAccelerate = sv_accelerate.GetFloat();
+ // if our wish speed is too low (attributes), we must increase acceleration or we'll never overcome friction
+ // Reverse the basic friction calculation to find our required acceleration
+ if ( flWishSpeed > 0 && flWishSpeed < CalcWishSpeedThreshold() )
+ {
+ // accelspeed = accel * gpGlobals->frametime * wishspeed * player->m_surfaceFriction;
+ // accelspeed > drop;
+ // drop = accel * frametime * wish * plFriction
+ // accel > drop / (wish * gametime * plFriction)
+ // drop = control * (plFriction * sv_friction) * gameTime;
+ // accel > control * sv_friction / wish
+ float flSpeed = VectorLength( mv->m_vecVelocity );
+ float flControl = (flSpeed < sv_stopspeed.GetFloat()) ? sv_stopspeed.GetFloat() : flSpeed;
+ flAccelerate = (flControl * sv_friction.GetFloat()) / flWishSpeed + 1;
+ }
+
+ Accelerate( vecWishDirection, flWishSpeed, flAccelerate );
+ Assert( mv->m_vecVelocity.z == 0.0f );
+
+ // Clamp the players speed in x,y.
+ float flNewSpeed = VectorLength( mv->m_vecVelocity );
+ if ( flNewSpeed > mv->m_flMaxSpeed )
+ {
+ float flScale = ( mv->m_flMaxSpeed / flNewSpeed );
+ mv->m_vecVelocity.x *= flScale;
+ mv->m_vecVelocity.y *= flScale;
+ }
+
+ float flForwardPull = m_pTFPlayer->GetMovementForwardPull();
+
+ if ( flForwardPull > 0.0f )
+ {
+ mv->m_vecVelocity += vecForward * flForwardPull;
+
+ if ( mv->m_vecVelocity.Length2D() > mv->m_flMaxSpeed )
+ {
+ VectorNormalize( mv->m_vecVelocity );
+ mv->m_vecVelocity *= mv->m_flMaxSpeed;
+ }
+ }
+
+ // Now reduce their backwards speed to some percent of max, if they are traveling backwards
+ // unless they are under some minimum, to not penalize deployed snipers or heavies
+ if ( tf_clamp_back_speed.GetFloat() < 1.0 && VectorLength( mv->m_vecVelocity ) > tf_clamp_back_speed_min.GetFloat() )
+ {
+ float flDot = DotProduct( vecForward, mv->m_vecVelocity );
+
+ // are we moving backwards at all?
+ if ( flDot < 0 )
+ {
+ Vector vecBackMove = vecForward * flDot;
+ Vector vecRightMove = vecRight * DotProduct( vecRight, mv->m_vecVelocity );
+
+ // clamp the back move vector if it is faster than max
+ float flBackSpeed = VectorLength( vecBackMove );
+ float flMaxBackSpeed = ( mv->m_flMaxSpeed * tf_clamp_back_speed.GetFloat() );
+
+ if ( flBackSpeed > flMaxBackSpeed )
+ {
+ vecBackMove *= flMaxBackSpeed / flBackSpeed;
+ }
+
+ // reassemble velocity
+ mv->m_vecVelocity = vecBackMove + vecRightMove;
+
+ // Re-run this to prevent crazy values (clients can induce this via usercmd viewangles hacking)
+ flNewSpeed = VectorLength( mv->m_vecVelocity );
+ if ( flNewSpeed > mv->m_flMaxSpeed )
+ {
+ float flScale = ( mv->m_flMaxSpeed / flNewSpeed );
+ mv->m_vecVelocity.x *= flScale;
+ mv->m_vecVelocity.y *= flScale;
+ }
+ }
+ }
+
+ // Add base velocity to the player's current velocity - base velocity = velocity from conveyors, etc.
+ VectorAdd( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
+
+ // Calculate the current speed and return if we are not really moving.
+ float flSpeed = VectorLength( mv->m_vecVelocity );
+ if ( flSpeed < 1.0f )
+ {
+ // I didn't remove the base velocity here since it wasn't moving us in the first place.
+ mv->m_vecVelocity.Init();
+ return;
+ }
+
+ // Calculate the destination.
+ Vector vecDestination;
+ vecDestination.x = mv->GetAbsOrigin().x + ( mv->m_vecVelocity.x * gpGlobals->frametime );
+ vecDestination.y = mv->GetAbsOrigin().y + ( mv->m_vecVelocity.y * gpGlobals->frametime );
+ vecDestination.z = mv->GetAbsOrigin().z;
+
+#ifdef GAME_DLL
+ // allow bot to approve position change for intentional movement
+ INextBot *bot = player->MyNextBotPointer();
+ if ( bot && bot->GetIntentionInterface()->IsPositionAllowed( bot, vecDestination ) == ANSWER_NO )
+ {
+ // rejected - stay put
+ return;
+ }
+#endif
+
+ // Try moving to the destination.
+ trace_t trace;
+ TracePlayerBBox( mv->GetAbsOrigin(), vecDestination, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace );
+ if ( trace.fraction == 1.0f )
+ {
+ // Made it to the destination (remove the base velocity).
+ mv->SetAbsOrigin( trace.endpos );
+ VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
+
+ // Save the wish velocity.
+ mv->m_outWishVel += ( vecWishDirection * flWishSpeed );
+
+ // Try and keep the player on the ground.
+ // NOTE YWB 7/5/07: Don't do this here, our version of CategorizePosition encompasses this test
+ // StayOnGround();
+
+#ifdef CLIENT_DLL
+ // Track how far we moved (if we're a Scout or an Engineer carrying a building).
+ CTFPlayer* pTFPlayer = ToTFPlayer( player );
+ if ( pTFPlayer->IsPlayerClass( TF_CLASS_SCOUT ) ||
+ ( pTFPlayer->IsPlayerClass( TF_CLASS_ENGINEER ) && pTFPlayer->m_Shared.IsCarryingObject() ) )
+ {
+ float fInchesToMeters = 0.0254f;
+ float fWorldScale = 0.25;
+ float fMeters = pTFPlayer->GetMetersRan();
+ float fMetersRan = flSpeed*fInchesToMeters*fWorldScale*gpGlobals->frametime;
+ pTFPlayer->SetMetersRan( fMeters + fMetersRan, gpGlobals->framecount );
+ }
+#endif
+ return;
+ }
+
+ CTFPlayer* pBumpPlayer = ToTFPlayer( trace.m_pEnt );
+ if ( pBumpPlayer )
+ {
+ m_pTFPlayer->m_Shared.EndCharge();
+ }
+
+ // Now try and do a step move.
+ StepMove( vecDestination, trace );
+
+ // Remove base velocity.
+ Vector baseVelocity = player->GetBaseVelocity();
+ VectorSubtract( mv->m_vecVelocity, baseVelocity, mv->m_vecVelocity );
+
+ CheckKartWallBumping();
+
+ // Save the wish velocity.
+ mv->m_outWishVel += ( vecWishDirection * flWishSpeed );
+
+ // Try and keep the player on the ground.
+ // NOTE YWB 7/5/07: Don't do this here, our version of CategorizePosition encompasses this test
+ // StayOnGround();
+
+#if 0
+ // Debugging!!!
+ Vector vecTestVelocity = mv->m_vecVelocity;
+ vecTestVelocity.z = 0.0f;
+ float flTestSpeed = VectorLength( vecTestVelocity );
+ if ( baseVelocity.IsZero() && ( flTestSpeed > ( mv->m_flMaxSpeed + 1.0f ) ) )
+ {
+ Msg( "Step Max Speed < %f\n", flTestSpeed );
+ }
+
+ if ( tf_showspeed.GetBool() )
+ {
+ Msg( "Speed=%f\n", flTestSpeed );
+ }
+
+#endif
+}
+
+void CTFGameMovement::CheckKartWallBumping()
+{
+ // Karts need to drop their velocity when they bump into things
+ if ( !m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
+ return;
+
+ const float flCurrentSpeed = m_pTFPlayer->GetCurrentTauntMoveSpeed();
+ const float flMaxSpeed = mv->m_vecVelocity.Length();
+ const float flClampedSpeed = clamp( flCurrentSpeed, -flMaxSpeed, flMaxSpeed );
+
+ m_pTFPlayer->SetCurrentTauntMoveSpeed( flClampedSpeed );
+ // We hit a wall at a good speed
+ if ( fabs( flCurrentSpeed ) > 100.f && ( flCurrentSpeed - flClampedSpeed > 100.f ) )
+ {
+ // Play a flinch to show we impacted something
+ bool bDashing = m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART_DASH );
+ m_pTFPlayer->DoAnimationEvent( PLAYERANIMEVENT_CUSTOM_GESTURE, bDashing ? ACT_KART_IMPACT_BIG : ACT_KART_IMPACT );
+
+ Vector vAim = m_pTFPlayer->GetLocalVelocity();
+ vAim.z = 0;
+ vAim.NormalizeInPlace();
+
+ // Handle hitting skybox (disappear).
+ trace_t pWallTrace;
+ UTIL_TraceLine( m_pTFPlayer->GetAbsOrigin(), m_pTFPlayer->GetAbsOrigin() + vAim * 64, MASK_SOLID, m_pTFPlayer, COLLISION_GROUP_DEBRIS, &pWallTrace );
+
+ // if we collide with a wall that is 90degrees or higher, bump backwards
+ if ( pWallTrace.fraction < 1.0 && !( pWallTrace.surface.flags & SURF_SKY ) && pWallTrace.m_pEnt && !pWallTrace.m_pEnt->IsPlayer() && pWallTrace.plane.normal.z <= 0 )
+ {
+#ifdef GAME_DLL
+ // Bounce off the wall, deflect in the direction of the normal of the surface that we collided with
+ Vector vOld = m_pTFPlayer->GetLocalVelocity();
+ Vector vNew = ( -2.0f * pWallTrace.plane.normal.Dot( vOld ) * pWallTrace.plane.normal + vOld );
+ vNew.NormalizeInPlace();
+ m_pTFPlayer->AddHalloweenKartPushEvent( m_pTFPlayer, NULL, NULL, vNew * vOld.Length() / 2.0f, 0 );
+ if ( bDashing )
+ {
+ // Stop moving
+ m_pTFPlayer->SetAbsVelocity( vec3_origin );
+ m_pTFPlayer->SetCurrentTauntMoveSpeed( 0 );
+ m_pTFPlayer->m_Shared.RemoveCond( TF_COND_HALLOWEEN_KART_DASH );
+ }
+
+ m_pTFPlayer->SetCurrentTauntMoveSpeed( 0.f );
+#endif
+
+#ifdef CLIENT_DLL
+ if ( bDashing )
+ {
+ m_pTFPlayer->EmitSound( "BumperCar.BumpHard" );
+ m_pTFPlayer->ParticleProp()->Create( "kart_impact_sparks", PATTACH_ABSORIGIN, NULL, vAim );
+ }
+ else
+ {
+ m_pTFPlayer->EmitSound( "BumperCar.Bump" );
+ m_pTFPlayer->ParticleProp()->Create( "kart_impact_sparks", PATTACH_ABSORIGIN, NULL, vAim );
+ }
+#endif
+ }
+ }
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CTFGameMovement::GetAirSpeedCap( void )
+{
+ if ( m_pTFPlayer->GetGrapplingHookTarget() )
+ {
+ if ( m_pTFPlayer->m_Shared.GetCarryingRuneType() == RUNE_AGILITY )
+ {
+ switch ( m_pTFPlayer->GetPlayerClass()->GetClassIndex() )
+ {
+ case TF_CLASS_SOLDIER:
+ case TF_CLASS_HEAVYWEAPONS:
+ return 850.f;
+ default:
+ return 950.f;
+ }
+ }
+
+ return tf_grapplinghook_move_speed.GetFloat();
+ }
+ else if ( m_pTFPlayer->m_Shared.InCond( TF_COND_SHIELD_CHARGE ) )
+ {
+ return tf_max_charge_speed.GetFloat();
+ }
+ else
+ {
+ float flCap = BaseClass::GetAirSpeedCap();
+/*
+#ifdef STAGING_ONLY
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_SPACE_GRAVITY ) )
+ {
+ flCap *= tf_space_aircontrol.GetFloat();
+ }
+#endif
+*/
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_PARACHUTE_DEPLOYED ) )
+ {
+ flCap *= tf_parachute_aircontrol.GetFloat();
+ }
+
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
+ {
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART_DASH ) )
+ {
+ return tf_halloween_kart_dash_speed.GetFloat();
+ }
+ flCap *= tf_halloween_kart_aircontrol.GetFloat();
+ }
+
+ float flIncreasedAirControl = 1.f;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pTFPlayer, flIncreasedAirControl, mod_air_control );
+
+ return ( flCap * flIncreasedAirControl );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGameMovement::AirMove( void )
+{
+ // check if grappling move should do step move
+ if ( m_pTFPlayer->GetGrapplingHookTarget() )
+ {
+ // Try moving to the destination.
+ Vector vecDestination = mv->GetAbsOrigin() + ( mv->m_vecVelocity * gpGlobals->frametime );
+ trace_t trace;
+ TracePlayerBBox( mv->GetAbsOrigin(), vecDestination, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace );
+ if ( trace.fraction != 1.f )
+ {
+ StepMove( vecDestination, trace );
+ return;
+ }
+ }
+
+ int i;
+ Vector wishvel;
+ float fmove, smove;
+ Vector wishdir;
+ float wishspeed;
+ Vector forward, right, up;
+
+ AngleVectors (mv->m_vecViewAngles, &forward, &right, &up); // Determine movement angles
+
+ // Copy movement amounts
+ fmove = mv->m_flForwardMove;
+ smove = mv->m_flSideMove;
+
+ // Zero out z components of movement vectors
+ forward[2] = 0;
+ right[2] = 0;
+ VectorNormalize(forward); // Normalize remainder of vectors
+ VectorNormalize(right); //
+
+ for (i=0 ; i<2 ; i++) // Determine x and y parts of velocity
+ wishvel[i] = forward[i]*fmove + right[i]*smove;
+ wishvel[2] = 0; // Zero out z part of velocity
+
+ VectorCopy (wishvel, wishdir); // Determine maginitude of speed of move
+ wishspeed = VectorNormalize(wishdir);
+
+ //
+ // clamp to server defined max speed
+ //
+ if ( wishspeed != 0 && (wishspeed > mv->m_flMaxSpeed))
+ {
+ VectorScale (wishvel, mv->m_flMaxSpeed/wishspeed, wishvel);
+ wishspeed = mv->m_flMaxSpeed;
+ }
+
+ float flAirAccel = sv_airaccelerate.GetFloat();
+/*
+#ifdef STAGING_ONLY
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_SPACE_GRAVITY ) )
+ {
+ flAirAccel *= tf_space_aircontrol.GetFloat();
+ }
+#endif
+*/
+ AirAccelerate( wishdir, wishspeed, flAirAccel );
+
+ float flForwardPull = m_pTFPlayer->GetMovementForwardPull();
+
+ if ( flForwardPull > 0.0f )
+ {
+ mv->m_vecVelocity += forward * flForwardPull;
+
+ if ( mv->m_vecVelocity.Length2D() > mv->m_flMaxSpeed )
+ {
+ float flZ = mv->m_vecVelocity.z;
+ mv->m_vecVelocity.z = 0.0f;
+ VectorNormalize( mv->m_vecVelocity );
+ mv->m_vecVelocity *= mv->m_flMaxSpeed;
+ mv->m_vecVelocity.z = flZ;
+ }
+ }
+
+ // Add in any base velocity to the current velocity.
+ VectorAdd( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
+
+ int iBlocked = TryPlayerMove();
+
+ // TryPlayerMove uses '2' to indictate wall colision wtf
+ if ( iBlocked & 2 )
+ {
+ CheckKartWallBumping();
+ }
+
+ // Now pull the base velocity back out. Base velocity is set if you are on a moving object, like a conveyor (or maybe another monster?)
+ VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
+}
+
+extern void TracePlayerBBoxForGround( const Vector& start, const Vector& end, const Vector& minsSrc,
+ const Vector& maxsSrc, IHandleEntity *player, unsigned int fMask,
+ int collisionGroup, trace_t& pm );
+
+
+//-----------------------------------------------------------------------------
+// This filter checks against buildable objects.
+//-----------------------------------------------------------------------------
+class CTraceFilterObject : public CTraceFilterSimple
+{
+public:
+ DECLARE_CLASS( CTraceFilterObject, CTraceFilterSimple );
+
+ CTraceFilterObject( const IHandleEntity *passentity, int collisionGroup );
+ virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask );
+};
+
+CTraceFilterObject::CTraceFilterObject( const IHandleEntity *passentity, int collisionGroup ) :
+BaseClass( passentity, collisionGroup )
+{
+
+}
+
+bool CTraceFilterObject::ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
+{
+ CBaseEntity *pMe = const_cast< CBaseEntity * >( EntityFromEntityHandle( GetPassEntity() ) );
+ CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
+
+ if ( pEntity )
+ {
+#ifdef STAGING_ONLY
+ // Special case stealth clips through all players and objects
+ CTFPlayer *pTFPlayerMe = ToTFPlayer( pMe );
+ if ( pTFPlayerMe && pTFPlayerMe->m_Shared.InCond( TF_COND_STEALTHED_PHASE ) )
+ {
+ // if we don't want to collide with anything, just remove this if
+ if ( pEntity->IsBaseObject() || pEntity->IsPlayer() )
+ {
+ return false;
+ }
+ }
+
+ if ( pEntity->IsPlayer() )
+ {
+ CTFPlayer *pTFPlayerThem = ToTFPlayer( pEntity );
+ if ( pTFPlayerThem && pTFPlayerThem->m_Shared.InCond( TF_COND_STEALTHED_PHASE ) )
+ return false;
+ }
+#endif // STAGING_ONLY
+
+ if ( pEntity->IsBaseObject() )
+ {
+ CBaseObject *pObject = assert_cast<CBaseObject *>( pEntity );
+ if ( pObject && pObject->GetOwner() == pMe )
+ {
+#ifdef GAME_DLL
+ // engineer-bots should not collide with their buildables to avoid nasty pathing issues
+ CTFPlayer *pOwner = ToTFPlayer( pMe );
+ if ( pOwner->IsBotOfType( TF_BOT_TYPE ) )
+ {
+ bool bHitObjectType = pObject->GetType() == OBJ_SENTRYGUN || pObject->GetType() == OBJ_DISPENSER;
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ bHitObjectType |= pObject->GetType() == OBJ_TELEPORTER;
+ }
+
+ if ( bHitObjectType )
+ {
+ // engineer bots not blocked by sentries or dispensers
+ return false;
+ }
+ }
+#endif
+ // my buildings are solid to me
+ return true;
+ }
+ }
+#ifdef GAME_DLL
+ else if ( pEntity->IsPlayer() )
+ {
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ CTFBot *bot = ToTFBot( pEntity );
+
+ if ( bot && ( bot->HasMission( CTFBot::MISSION_DESTROY_SENTRIES ) || bot->HasMission( CTFBot::MISSION_REPROGRAMMED ) ) )
+ {
+ // Don't collide with sentry busters since they don't collide with us
+ return false;
+ }
+
+ CTFBot *meBot = ToTFBot( pMe );
+
+ if ( meBot && ( meBot->HasMission( CTFBot::MISSION_DESTROY_SENTRIES ) || meBot->HasMission( CTFBot::MISSION_REPROGRAMMED ) ) )
+ {
+ // Sentry Busters don't collide with enemies (so they can't be body-blocked)
+ return false;
+ }
+ }
+ }
+ else if ( pEntity->MyNextBotPointer() && !pEntity->MyNextBotPointer()->GetLocomotionInterface()->ShouldCollideWith( pMe ) )
+ {
+ return false;
+ }
+#endif
+ }
+
+ return CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask );
+}
+
+CBaseHandle CTFGameMovement::TestPlayerPosition( const Vector& pos, int collisionGroup, trace_t& pm )
+{
+ if( tf_solidobjects.GetBool() == false )
+ return BaseClass::TestPlayerPosition( pos, collisionGroup, pm );
+
+ Ray_t ray;
+ ray.Init( pos, pos, GetPlayerMins(), GetPlayerMaxs() );
+
+ CTraceFilterObject traceFilter( mv->m_nPlayerHandle.Get(), collisionGroup );
+ enginetrace->TraceRay( ray, PlayerSolidMask(), &traceFilter, &pm );
+
+ if ( (pm.contents & PlayerSolidMask()) && pm.m_pEnt )
+ {
+ return pm.m_pEnt->GetRefEHandle();
+ }
+ else
+ {
+ return INVALID_EHANDLE_INDEX;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Traces player movement + position
+//-----------------------------------------------------------------------------
+void CTFGameMovement::TracePlayerBBox( const Vector& start, const Vector& end, unsigned int fMask, int collisionGroup, trace_t& pm )
+{
+ if( tf_solidobjects.GetBool() == false )
+ return BaseClass::TracePlayerBBox( start, end, fMask, collisionGroup, pm );
+
+ Ray_t ray;
+ ray.Init( start, end, GetPlayerMins(), GetPlayerMaxs() );
+
+ CTraceFilterObject traceFilter( mv->m_nPlayerHandle.Get(), collisionGroup );
+
+ enginetrace->TraceRay( ray, fMask, &traceFilter, &pm );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &input -
+//-----------------------------------------------------------------------------
+void CTFGameMovement::CategorizePosition( void )
+{
+ // Observer.
+ if ( player->IsObserver() )
+ return;
+
+ // Reset this each time we-recategorize, otherwise we have bogus friction when we jump into water and plunge downward really quickly
+ player->m_surfaceFriction = 1.0f;
+
+ // Doing this before we move may introduce a potential latency in water detection, but
+ // doing it after can get us stuck on the bottom in water if the amount we move up
+ // is less than the 1 pixel 'threshold' we're about to snap to. Also, we'll call
+ // this several times per frame, so we really need to avoid sticking to the bottom of
+ // water on each call, and the converse case will correct itself if called twice.
+ CheckWater();
+
+ // If standing on a ladder we are not on ground.
+ if ( player->GetMoveType() == MOVETYPE_LADDER )
+ {
+ SetGroundEntity( NULL );
+ return;
+ }
+
+ // Check for a jump.
+ if ( mv->m_vecVelocity.z > 250.0f )
+ {
+#if defined(GAME_DLL)
+ if ( m_pTFPlayer->m_bTakenBlastDamageSinceLastMovement )
+ {
+ m_pTFPlayer->SetBlastJumpState( TF_PLAYER_ENEMY_BLASTED_ME );
+ }
+#endif
+
+ SetGroundEntity( NULL );
+ return;
+ }
+
+ // Calculate the start and end position.
+ Vector vecStartPos = mv->GetAbsOrigin();
+ Vector vecEndPos( mv->GetAbsOrigin().x, mv->GetAbsOrigin().y, ( mv->GetAbsOrigin().z - 2.0f ) );
+
+ // NOTE YWB 7/5/07: Since we're already doing a traceline here, we'll subsume the StayOnGround (stair debouncing) check into the main traceline we do here to see what we're standing on
+ bool bUnderwater = ( player->GetWaterLevel() >= WL_Eyes );
+ bool bMoveToEndPos = false;
+ if ( player->GetMoveType() == MOVETYPE_WALK &&
+ player->GetGroundEntity() != NULL && !bUnderwater )
+ {
+ // if walking and still think we're on ground, we'll extend trace down by stepsize so we don't bounce down slopes
+ vecEndPos.z -= player->GetStepSize();
+ bMoveToEndPos = true;
+ }
+
+ trace_t trace;
+ TracePlayerBBox( vecStartPos, vecEndPos, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace );
+
+ // Steep plane, not on ground.
+ if ( trace.plane.normal.z < 0.7f )
+ {
+ // Test four sub-boxes, to see if any of them would have found shallower slope we could actually stand on.
+ TracePlayerBBoxForGround( vecStartPos, vecEndPos, GetPlayerMins(), GetPlayerMaxs(), mv->m_nPlayerHandle.Get(), PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace );
+
+ if ( trace.plane.normal[2] < 0.7f )
+ {
+ // Too steep.
+ SetGroundEntity( NULL );
+ if ( ( mv->m_vecVelocity.z > 0.0f ) &&
+ ( player->GetMoveType() != MOVETYPE_NOCLIP ) )
+ {
+ player->m_surfaceFriction = 0.25f;
+ }
+ }
+ else
+ {
+ SetGroundEntity( &trace );
+ }
+ }
+ else
+ {
+ // YWB: This logic block essentially lifted from StayOnGround implementation
+ if ( bMoveToEndPos &&
+ !trace.startsolid && // not sure we need this check as fraction would == 0.0f?
+ trace.fraction > 0.0f && // must go somewhere
+ trace.fraction < 1.0f ) // must hit something
+ {
+ float flDelta = fabs( mv->GetAbsOrigin().z - trace.endpos.z );
+ // HACK HACK: The real problem is that trace returning that strange value
+ // we can't network over based on bit precision of networking origins
+ if ( flDelta > 0.5f * COORD_RESOLUTION )
+ {
+ Vector org = mv->GetAbsOrigin();
+ org.z = trace.endpos.z;
+ mv->SetAbsOrigin( org );
+ }
+ }
+ SetGroundEntity( &trace );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGameMovement::CheckWaterJump( void )
+{
+ Vector flatforward;
+ Vector flatvelocity;
+ float curspeed;
+
+ // Jump button down?
+ bool bJump = ( ( mv->m_nButtons & IN_JUMP ) != 0 );
+
+ Vector forward, right;
+ AngleVectors( mv->m_vecViewAngles, &forward, &right, NULL ); // Determine movement angles
+
+ // Already water jumping.
+ if (player->m_flWaterJumpTime)
+ return;
+
+ // Don't hop out if we just jumped in
+ if (mv->m_vecVelocity[2] < -180)
+ return; // only hop out if we are moving up
+
+ // See if we are backing up
+ flatvelocity[0] = mv->m_vecVelocity[0];
+ flatvelocity[1] = mv->m_vecVelocity[1];
+ flatvelocity[2] = 0;
+
+ // Must be moving
+ curspeed = VectorNormalize( flatvelocity );
+
+#if 1
+ // Copy movement amounts
+ float fmove = mv->m_flForwardMove;
+ float smove = mv->m_flSideMove;
+
+ for ( int iAxis = 0; iAxis < 2; ++iAxis )
+ {
+ flatforward[iAxis] = forward[iAxis] * fmove + right[iAxis] * smove;
+ }
+#else
+ // see if near an edge
+ flatforward[0] = forward[0];
+ flatforward[1] = forward[1];
+#endif
+ flatforward[2] = 0;
+ VectorNormalize( flatforward );
+
+ // Are we backing into water from steps or something? If so, don't pop forward
+ if ( curspeed != 0.0 && ( DotProduct( flatvelocity, flatforward ) < 0.0 ) && !bJump )
+ return;
+
+ Vector vecStart;
+ // Start line trace at waist height (using the center of the player for this here)
+ vecStart = mv->GetAbsOrigin() + (GetPlayerMins() + GetPlayerMaxs() ) * 0.5;
+
+ Vector vecEnd;
+ VectorMA( vecStart, TF_WATERJUMP_FORWARD/*tf_waterjump_forward.GetFloat()*/, flatforward, vecEnd );
+
+ trace_t tr;
+ TracePlayerBBox( vecStart, vecEnd, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, tr );
+ if ( tr.fraction < 1.0 ) // solid at waist
+ {
+ IPhysicsObject *pPhysObj = tr.m_pEnt->VPhysicsGetObject();
+ if ( pPhysObj )
+ {
+ if ( pPhysObj->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
+ return;
+ }
+
+ vecStart.z = mv->GetAbsOrigin().z + player->GetViewOffset().z + WATERJUMP_HEIGHT;
+ VectorMA( vecStart, TF_WATERJUMP_FORWARD/*tf_waterjump_forward.GetFloat()*/, flatforward, vecEnd );
+ VectorMA( vec3_origin, -50.0f, tr.plane.normal, player->m_vecWaterJumpVel );
+
+ TracePlayerBBox( vecStart, vecEnd, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, tr );
+ if ( tr.fraction == 1.0 ) // open at eye level
+ {
+ // Now trace down to see if we would actually land on a standable surface.
+ VectorCopy( vecEnd, vecStart );
+ vecEnd.z -= 1024.0f;
+ TracePlayerBBox( vecStart, vecEnd, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, tr );
+ if ( ( tr.fraction < 1.0f ) && ( tr.plane.normal.z >= 0.7 ) )
+ {
+ mv->m_vecVelocity[2] = TF_WATERJUMP_UP/*tf_waterjump_up.GetFloat()*/; // Push up
+ mv->m_nOldButtons |= IN_JUMP; // Don't jump again until released
+ player->AddFlag( FL_WATERJUMP );
+ player->m_flWaterJumpTime = 2000.0f; // Do this for 2 seconds
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGameMovement::CheckFalling( void )
+{
+ // if we landed on the ground
+ if ( player->GetGroundEntity() != NULL && !IsDead() )
+ {
+ // turn off the jumping flag if we're on ground after a jump
+ if ( m_pTFPlayer->m_Shared.IsJumping() )
+ {
+ m_pTFPlayer->m_Shared.SetJumping( false );
+
+#ifdef CLIENT_DLL
+ IGameEvent *event = gameeventmanager->CreateEvent( "landed" );
+ if ( event && m_pTFPlayer->IsLocalPlayer() )
+ {
+ event->SetInt( "player", m_pTFPlayer->GetUserID() );
+ gameeventmanager->FireEventClientSide( event );
+ }
+#endif // CLIENT_DLL
+ }
+ }
+
+ BaseClass::CheckFalling();
+}
+
+void CTFGameMovement::FullWalkMoveUnderwater()
+{
+ if ( player->GetWaterLevel() == WL_Waist )
+ {
+ CheckWaterJump();
+ }
+
+ // If we are falling again, then we must not trying to jump out of water any more.
+ if ( ( mv->m_vecVelocity.z < 0.0f ) && player->m_flWaterJumpTime )
+ {
+ player->m_flWaterJumpTime = 0.0f;
+ }
+
+ // Was jump button pressed?
+ if ( mv->m_nButtons & IN_JUMP )
+ {
+ CheckJumpButton();
+ }
+ else
+ {
+ mv->m_nOldButtons &= ~IN_JUMP;
+ }
+
+ // Perform regular water movement
+ WaterMove();
+
+ // Redetermine position vars
+ CategorizePosition();
+
+ // If we are on ground, no downward velocity.
+ if ( player->GetGroundEntity() != NULL )
+ {
+ mv->m_vecVelocity[2] = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGameMovement::FullWalkMove()
+{
+ if ( !InWater() )
+ {
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_PARACHUTE_DEPLOYED ) && mv->m_vecVelocity[2] < 0 )
+ {
+ mv->m_vecVelocity[2] = Max( mv->m_vecVelocity[2], tf_parachute_maxspeed_z.GetFloat() );
+
+ float flDrag = tf_parachute_maxspeed_xy.GetFloat();
+ // Instead of clamping, we'll dampen
+ float flSpeedX = abs( mv->m_vecVelocity[0] );
+ float flSpeedY = abs( mv->m_vecVelocity[1] );
+ float flReductionX = flSpeedX > flDrag ? ( flSpeedX - flDrag ) / 3.0f - 10.0f : 0;
+ float flReductionY = flSpeedY > flDrag ? ( flSpeedY - flDrag ) / 3.0f - 10.0f : 0;
+
+ mv->m_vecVelocity[0] = Clamp( mv->m_vecVelocity[0], -flDrag - flReductionX, flDrag + flReductionX );
+ mv->m_vecVelocity[1] = Clamp( mv->m_vecVelocity[1], -flDrag - flReductionY, flDrag + flReductionY );
+ }
+
+ StartGravity();
+ }
+
+ // If we are leaping out of the water, just update the counters.
+ if ( player->m_flWaterJumpTime )
+ {
+ // Try to jump out of the water (and check to see if we still are).
+ WaterJump();
+ TryPlayerMove();
+ CheckWater();
+ return;
+ }
+
+ // If we are swimming in the water, see if we are nudging against a place we can jump up out
+ // of, and, if so, start out jump. Otherwise, if we are not moving up, then reset jump timer to 0.
+ // Also run the swim code if we're a ghost or have the TF_COND_SWIMMING_NO_EFFECTS condition
+ if ( InWater() || ( m_pTFPlayer && ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) || m_pTFPlayer->m_Shared.InCond( TF_COND_SWIMMING_NO_EFFECTS ) ) ) )
+ {
+ FullWalkMoveUnderwater();
+ return;
+ }
+
+ if (mv->m_nButtons & IN_JUMP)
+ {
+ CheckJumpButton();
+ }
+ else
+ {
+ mv->m_nOldButtons &= ~IN_JUMP;
+ }
+
+ // Make sure velocity is valid.
+ CheckVelocity();
+
+ if (player->GetGroundEntity() != NULL)
+ {
+ mv->m_vecVelocity[2] = 0.0;
+ Friction();
+ WalkMove();
+ }
+ else
+ {
+ AirMove();
+ }
+
+ // Set final flags.
+ CategorizePosition();
+
+ // Add any remaining gravitational component if we are not in water.
+ if ( !InWater() )
+ {
+ FinishGravity();
+ }
+
+ // If we are on ground, no downward velocity.
+ if ( player->GetGroundEntity() != NULL )
+ {
+ mv->m_vecVelocity[2] = 0;
+ }
+
+ // Handling falling.
+ CheckFalling();
+
+ // Make sure velocity is valid.
+ CheckVelocity();
+
+// #ifdef GAME_DLL
+// if ( m_pTFPlayer->IsPlayerClass( TF_CLASS_SCOUT ) )
+// {
+// CTFWeaponBase* pWeapon = m_pTFPlayer->GetActiveTFWeapon();
+// if ( pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_SODA_POPPER )
+// {
+// float speed = VectorLength( mv->m_vecVelocity );
+// float fDist = speed*gpGlobals->frametime;
+// float fHype = m_pTFPlayer->m_Shared.GetScoutHypeMeter() + (fDist / tf_scout_hype_mod.GetFloat());
+// if ( fHype > 100.f )
+// fHype = 100.f;
+// m_pTFPlayer->m_Shared.SetScoutHypeMeter( fHype );
+// }
+// }
+// #endif
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGameMovement::FullTossMove( void )
+{
+ trace_t pm;
+ Vector move;
+
+ // add velocity if player is moving
+ if ( (mv->m_flForwardMove != 0.0f) || (mv->m_flSideMove != 0.0f) || (mv->m_flUpMove != 0.0f))
+ {
+ Vector forward, right, up;
+ float fmove, smove;
+ Vector wishdir, wishvel;
+ float wishspeed;
+ int i;
+
+ AngleVectors (mv->m_vecViewAngles, &forward, &right, &up); // Determine movement angles
+
+ // Copy movement amounts
+ fmove = mv->m_flForwardMove;
+ smove = mv->m_flSideMove;
+
+ VectorNormalize (forward); // Normalize remainder of vectors.
+ VectorNormalize (right); //
+
+ for (i=0 ; i<3 ; i++) // Determine x and y parts of velocity
+ wishvel[i] = forward[i]*fmove + right[i]*smove;
+
+ wishvel[2] += mv->m_flUpMove;
+
+ VectorCopy (wishvel, wishdir); // Determine maginitude of speed of move
+ wishspeed = VectorNormalize(wishdir);
+
+ //
+ // Clamp to server defined max speed
+ //
+ if (wishspeed > mv->m_flMaxSpeed)
+ {
+ VectorScale (wishvel, mv->m_flMaxSpeed/wishspeed, wishvel);
+ wishspeed = mv->m_flMaxSpeed;
+ }
+
+ // Set pmove velocity
+ Accelerate ( wishdir, wishspeed, sv_accelerate.GetFloat() );
+ }
+
+ if ( mv->m_vecVelocity[2] > 0 )
+ {
+ SetGroundEntity( NULL );
+ }
+
+ // If on ground and not moving, return.
+ if ( player->GetGroundEntity() != NULL )
+ {
+ if (VectorCompare(player->GetBaseVelocity(), vec3_origin) &&
+ VectorCompare(mv->m_vecVelocity, vec3_origin))
+ return;
+ }
+
+ CheckVelocity();
+
+ // add gravity
+ if ( player->GetMoveType() == MOVETYPE_FLYGRAVITY )
+ {
+ AddGravity();
+ }
+
+ // move origin
+ // Base velocity is not properly accounted for since this entity will move again after the bounce without
+ // taking it into account
+ VectorAdd (mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity);
+
+ CheckVelocity();
+
+ VectorScale (mv->m_vecVelocity, gpGlobals->frametime, move);
+ VectorSubtract (mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity);
+
+ PushEntity( move, &pm ); // Should this clear basevelocity
+
+ CheckVelocity();
+
+ if (pm.allsolid)
+ {
+ // entity is trapped in another solid
+ SetGroundEntity( &pm );
+ mv->m_vecVelocity.Init();
+ return;
+ }
+
+ if ( pm.fraction != 1.0f )
+ {
+ PerformFlyCollisionResolution( pm, move );
+ }
+
+ // Check for in water
+ CheckWater();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Does the basic move attempting to climb up step heights. It uses
+// the mv->GetAbsOrigin() and mv->m_vecVelocity. It returns a new
+// new mv->GetAbsOrigin(), mv->m_vecVelocity, and mv->m_outStepHeight.
+//-----------------------------------------------------------------------------
+void CTFGameMovement::StepMove( Vector &vecDestination, trace_t &trace )
+{
+ trace_t saveTrace;
+ saveTrace = trace;
+
+ Vector vecEndPos;
+ VectorCopy( vecDestination, vecEndPos );
+
+ Vector vecPos, vecVel;
+ VectorCopy( mv->GetAbsOrigin(), vecPos );
+ VectorCopy( mv->m_vecVelocity, vecVel );
+
+ bool bLowRoad = false;
+ bool bUpRoad = true;
+
+ // First try the "high road" where we move up and over obstacles
+ if ( player->m_Local.m_bAllowAutoMovement )
+ {
+ // Trace up by step height
+ VectorCopy( mv->GetAbsOrigin(), vecEndPos );
+ vecEndPos.z += player->m_Local.m_flStepSize + DIST_EPSILON;
+ TracePlayerBBox( mv->GetAbsOrigin(), vecEndPos, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace );
+ if ( !trace.startsolid && !trace.allsolid )
+ {
+ mv->SetAbsOrigin( trace.endpos );
+ }
+
+ // Trace over from there
+ TryPlayerMove();
+
+ // Then trace back down by step height to get final position
+ VectorCopy( mv->GetAbsOrigin(), vecEndPos );
+ vecEndPos.z -= player->m_Local.m_flStepSize + DIST_EPSILON;
+ TracePlayerBBox( mv->GetAbsOrigin(), vecEndPos, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace );
+ // If the trace ended up in empty space, copy the end over to the origin.
+ if ( !trace.startsolid && !trace.allsolid )
+ {
+ mv->SetAbsOrigin( trace.endpos );
+ }
+
+ // If we are not on the standable ground any more or going the "high road" didn't move us at all, then we'll also want to check the "low road"
+ if ( ( trace.fraction != 1.0f &&
+ trace.plane.normal[2] < 0.7 ) || VectorCompare( mv->GetAbsOrigin(), vecPos ) )
+ {
+ bLowRoad = true;
+ bUpRoad = false;
+ }
+ }
+ else
+ {
+ bLowRoad = true;
+ bUpRoad = false;
+ }
+
+ if ( bLowRoad )
+ {
+ // Save off upward results
+ Vector vecUpPos, vecUpVel;
+ if ( bUpRoad )
+ {
+ VectorCopy( mv->GetAbsOrigin(), vecUpPos );
+ VectorCopy( mv->m_vecVelocity, vecUpVel );
+ }
+
+ // Take the "low" road
+ mv->SetAbsOrigin( vecPos );
+ VectorCopy( vecVel, mv->m_vecVelocity );
+ VectorCopy( vecDestination, vecEndPos );
+ TryPlayerMove( &vecEndPos, &saveTrace );
+
+ // Down results.
+ Vector vecDownPos, vecDownVel;
+ VectorCopy( mv->GetAbsOrigin(), vecDownPos );
+ VectorCopy( mv->m_vecVelocity, vecDownVel );
+
+ if ( bUpRoad )
+ {
+ float flUpDist = ( vecUpPos.x - vecPos.x ) * ( vecUpPos.x - vecPos.x ) + ( vecUpPos.y - vecPos.y ) * ( vecUpPos.y - vecPos.y );
+ float flDownDist = ( vecDownPos.x - vecPos.x ) * ( vecDownPos.x - vecPos.x ) + ( vecDownPos.y - vecPos.y ) * ( vecDownPos.y - vecPos.y );
+
+ // decide which one went farther
+ if ( flUpDist >= flDownDist )
+ {
+ mv->SetAbsOrigin( vecUpPos );
+ VectorCopy( vecUpVel, mv->m_vecVelocity );
+
+ // copy z value from the Low Road move
+ mv->m_vecVelocity.z = vecDownVel.z;
+ }
+ }
+ }
+
+ float flStepDist = mv->GetAbsOrigin().z - vecPos.z;
+ if ( flStepDist > 0 )
+ {
+ mv->m_outStepHeight += flStepDist;
+ }
+}
+
+bool CTFGameMovement::GameHasLadders() const
+{
+ return false;
+}
+
+void CTFGameMovement::SetGroundEntity( trace_t *pm )
+{
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) && !m_pTFPlayer->GetGroundEntity() && pm && pm->m_pEnt )
+ {
+ m_pTFPlayer->EmitSound( "BumperCar.JumpLand" );
+ }
+
+ BaseClass::SetGroundEntity( pm );
+ if ( pm && pm->m_pEnt )
+ {
+#ifdef GAME_DLL
+ int iAirDash = m_pTFPlayer->m_Shared.GetAirDash();
+ if ( iAirDash > 0 )
+ {
+ m_pTFPlayer->SpeakConceptIfAllowed( MP_CONCEPT_DOUBLE_JUMP, "started_jumping:0" );
+ }
+ m_pTFPlayer->m_Shared.SetWeaponKnockbackID( -1 );
+ m_pTFPlayer->m_bScattergunJump = false;
+#endif // GAME_DLL
+ m_pTFPlayer->m_Shared.SetAirDash( 0 );
+ m_pTFPlayer->m_Shared.SetAirDucked( 0 );
+
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_GRAPPLINGHOOK_SAFEFALL ) )
+ {
+ // CheckFalling happens after this. reset the fall velocity to prevent fall damage
+ if ( tf_grapplinghook_prevent_fall_damage.GetBool() )
+ player->m_Local.m_flFallVelocity = 0;
+
+ m_pTFPlayer->m_Shared.RemoveCond( TF_COND_GRAPPLINGHOOK_SAFEFALL );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGameMovement::PlayerRoughLandingEffects( float fvol )
+{
+ if ( m_pTFPlayer )
+ {
+/*
+#ifdef STAGING_ONLY
+ // No impact effects if we're in space low-grav
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_SPACE_GRAVITY ) )
+ {
+ return;
+ }
+#endif // STAGING_ONLY
+*/
+ // don't play landing sound when grappling hook into a surface
+ if ( m_pTFPlayer->m_Shared.InCond( TF_COND_GRAPPLINGHOOK ) )
+ {
+ return;
+ }
+
+ if ( m_pTFPlayer->IsPlayerClass(TF_CLASS_SCOUT) )
+ {
+ // Scouts don't play rumble unless they take damage.
+ if ( fvol < 1.0 )
+ {
+ fvol = 0;
+ }
+ }
+ }
+
+ BaseClass::PlayerRoughLandingEffects( fvol );
+}
+
+#if 0
+// Not being used currently - part of TestDuck!
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGameMovement::HandleDuck( int nButtonsPressed )
+{
+ // XBOX SERVER ONLY
+#if !defined(CLIENT_DLL)
+ if ( IsX360() && nButtonsPressed & IN_DUCK )
+ {
+ // Hinting logic
+ if ( player->GetToggledDuckState() && player->m_nNumCrouches < NUM_CROUCH_HINTS )
+ {
+ UTIL_HudHintText( player, "#Valve_Hint_Crouch" );
+ player->m_nNumCrouches++;
+ }
+ }
+#endif
+
+ bool bInAir = ( player->GetGroundEntity() == NULL );
+ bool bInDuck = ( player->GetFlags() & FL_DUCKING ) ? true : false;
+
+ // Starting a duck.
+ if ( ( nButtonsPressed & IN_DUCK ) && !bInDuck )
+ {
+ if ( !player->m_Local.m_bDucking )
+ {
+ player->m_Local.m_flDucktime = TIME_TO_DUCK_MS;
+ player->m_Local.m_bDucking = true;
+ }
+ else
+ {
+ // Find unduck percentage and calcluate the duck time.
+ float flPercentage = player->m_Local.m_flDucktime / TIME_TO_UNDUCK_MS;
+ player->m_Local.m_flDucktime = TIME_TO_DUCK_MS * ( 1.0f - flPercentage );
+ }
+
+ if ( m_pTFPlayer->m_Shared.GetAirDash() > 0 )
+ {
+ m_pTFPlayer->DoAnimationEvent( PLAYERANIMEVENT_DOUBLEJUMP_CROUCH );
+ }
+ }
+
+ // Handle the ducking.
+ if ( player->m_Local.m_bDucking )
+ {
+ // Finish in duck transition when transition time is over, in "duck", in air.
+ if ( ( player->m_Local.m_flDucktime <= 0.0f ) || bInDuck || bInAir )
+ {
+ FinishDuck();
+ }
+ else
+ {
+ // Calculate the eye offset.
+ float flDuckFraction = SimpleSpline( 1.0f - ( player->m_Local.m_flDucktime / TIME_TO_DUCK_MS ) );
+ SetDuckedEyeOffset( flDuckFraction );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGameMovement::HandleUnDuck( int nButtonsReleased )
+{
+ if ( !player->m_Local.m_bAllowAutoMovement )
+ return;
+
+ bool bInAir = ( player->GetGroundEntity() == NULL );
+ bool bInDuck = ( player->GetFlags() & FL_DUCKING ) ? true : false;
+
+ // Ending a duck (or trying to).
+ if ( nButtonsReleased & IN_DUCK )
+ {
+ if ( bInDuck )
+ {
+ player->m_Local.m_flDucktime = TIME_TO_UNDUCK_MS;
+ player->m_Local.m_bDucking = true;
+ }
+ else if ( player->m_Local.m_bDucking )
+ {
+ // Find unduck percentage and calcluate the duck time.
+ float flPercentage = player->m_Local.m_flDucktime / TIME_TO_DUCK_MS;
+ player->m_Local.m_flDucktime = TIME_TO_UNDUCK_MS * ( 1.0f - flPercentage );
+ }
+ }
+
+ // Check to see if we are capable of unducking given our environment.
+ if ( CanUnduck() )
+ {
+ if ( ( player->m_Local.m_bDucking || player->m_Local.m_bDucked ) )
+ {
+ // We are unducking now.
+ player->m_Local.m_bDucking = true;
+
+ // Finish ducking immediately if duck time is over or we are in the air.
+ if ( player->m_Local.m_flDucktime <= 0.0f || bInAir )
+ {
+ FinishUnDuck();
+ }
+ else
+ {
+ // Calculate the eye offset.
+ float flDuckFraction = SimpleSpline( ( player->m_Local.m_flDucktime / TIME_TO_UNDUCK_MS ) );
+ SetDuckedEyeOffset( flDuckFraction );
+ }
+ }
+ }
+ else
+ {
+ // Under something where we cannot unduck - rest.
+ if ( player->m_Local.m_flDucktime != TIME_TO_UNDUCK_MS )
+ {
+ player->m_Local.m_flDucktime = TIME_TO_UNDUCK_MS;
+ player->m_Local.m_bDucked = true;
+ player->m_Local.m_bDucking = false;
+ player->AddFlag( FL_DUCKING );
+
+ // Reset the eye offset.
+ SetDuckedEyeOffset( 1.0f );
+ }
+ }
+}
+
+void CTFGameMovement::TestDuck( )
+{
+
+ // Handle buttons.
+ int nButtonsChanged = ( mv->m_nOldButtons ^ mv->m_nButtons );
+ int nButtonsPressed = nButtonsChanged & mv->m_nButtons;
+ int nButtonsReleased = nButtonsChanged & mv->m_nOldButtons;
+ if ( mv->m_nButtons & IN_DUCK )
+ {
+ mv->m_nOldButtons |= IN_DUCK;
+ }
+ else
+ {
+ mv->m_nOldButtons &= ~IN_DUCK;
+ }
+
+ // Handle death.
+ if ( IsDead() )
+ return;
+ // Slow down ducked players.
+ HandleDuckingSpeedCrop();
+
+ // In some ducked state - button press to duck, duck transitions, or fully ducked.
+ bool bInDuck = ( player->GetFlags() & FL_DUCKING ) ? true : false;
+ if ( ( mv->m_nButtons & IN_DUCK ) || player->m_Local.m_bDucking || bInDuck )
+ {
+ // Duck State
+ if ( ( mv->m_nButtons & IN_DUCK ) )
+ {
+ HandleDuck( nButtonsPressed );
+ }
+ // Unduck State.
+ else
+ {
+ HandleUnDuck( nButtonsReleased );
+ }
+ }
+}
+#endif
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGameMovement::DuckOverrides()
+{
+ bool bOnGround = ( player->GetGroundEntity() != NULL );
+
+ // Don't allowing ducking in water.
+ if ( ( ( player->GetWaterLevel() >= WL_Feet ) && !bOnGround ) ||
+ player->GetWaterLevel() >= WL_Eyes )
+ {
+ mv->m_nButtons &= ~IN_DUCK;
+ }
+
+ if ( !tf_clamp_airducks.GetBool() )
+ return;
+
+ // Check the duck timer and disable the duck button.
+ if ( gpGlobals->curtime < m_pTFPlayer->m_Shared.GetDuckTimer() && bOnGround )
+ {
+ mv->m_nButtons &= ~IN_DUCK;
+ }
+
+ // If we're trying to stand up, don't let the player try to re-duck. This
+ // prevents what the community calls the "Quantum Crouch". The above ducktimer
+ // covers most of the cases where users play nice and duck and unduck while standing.
+ // The "Quantum Crouch" occurs when users do the following:
+ // 0: Get a Dispenser or other waist-high platform in front of you
+ // 1: Press Jump + Crouch and move towards the platform
+ // 2: Release Crouch while jumping
+ // ( this causes the duck timer to start counting down )
+ // 3: Land on the platform
+ // 4: While starting to stand up, press Crouch
+ // ( when the duck timer finishes, your view will be locked )
+ // The intent of the duck timer is to require you to stand up after you've started
+ // to unduck and to throttle duck spamming. This just enforces the unduck
+ // requirement.
+ if ( player->m_Local.m_bDucked && player->m_Local.m_bDucking )
+ {
+ mv->m_nButtons &= ~IN_DUCK;
+ }
+
+ // Only allow one duck per air event.
+ if ( !bOnGround && m_pTFPlayer->m_Shared.AirDuckedCount() >= TF_AIRDUCKED_COUNT )
+ {
+ mv->m_nButtons &= ~IN_DUCK;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGameMovement::OnDuck( int nButtonsPressed )
+{
+ // Check to see if we are in the air or ducking.
+ bool bInAir = ( player->GetGroundEntity() == NULL );
+ bool bInDuck = ( player->GetFlags() & FL_DUCKING ) ? true : false;
+
+ // XBOX SERVER ONLY
+#if !defined(CLIENT_DLL)
+ if ( IsX360() && nButtonsPressed & IN_DUCK )
+ {
+ // Hinting logic
+ if ( player->GetToggledDuckState() && player->m_nNumCrouches < NUM_CROUCH_HINTS )
+ {
+ UTIL_HudHintText( player, "#Valve_Hint_Crouch" );
+ player->m_nNumCrouches++;
+ }
+ }
+#endif
+
+ // Have the duck button pressed, but the player currently isn't in the duck position.
+ if ( ( nButtonsPressed & IN_DUCK ) && !bInDuck )
+ {
+ player->m_Local.m_flDucktime = GAMEMOVEMENT_DUCK_TIME;
+ player->m_Local.m_bDucking = true;
+
+ if ( m_pTFPlayer->m_Shared.GetAirDash() > 0 )
+ {
+ m_pTFPlayer->DoAnimationEvent( PLAYERANIMEVENT_DOUBLEJUMP_CROUCH );
+ }
+ }
+
+ // The player is in duck transition and not duck-jumping.
+ if ( player->m_Local.m_bDucking )
+ {
+ float flDuckMilliseconds = MAX( 0.0f, GAMEMOVEMENT_DUCK_TIME - ( float )player->m_Local.m_flDucktime );
+ float flDuckSeconds = flDuckMilliseconds * 0.001f;
+
+ // Finish in duck transition when transition time is over, in "duck", in air.
+ if ( ( flDuckSeconds > TIME_TO_DUCK ) || bInDuck || bInAir )
+ {
+ FinishDuck();
+ }
+ else
+ {
+ // Calc parametric time
+ float flDuckFraction = SimpleSpline( flDuckSeconds / TIME_TO_DUCK );
+ SetDuckedEyeOffset( flDuckFraction );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGameMovement::OnUnDuck( int nButtonsReleased )
+{
+ // Check to see if we are in the air or ducking.
+ bool bInAir = ( player->GetGroundEntity() == NULL );
+ bool bInDuck = ( player->GetFlags() & FL_DUCKING ) ? true : false;
+
+ // Once the duck button is released, start a timer. The player will not be able to engage in a duck
+ // until the timer expires. In addition, set that we have ducked in air (will be allowed only once
+ // while in air).
+ if ( nButtonsReleased & IN_DUCK )
+ {
+ m_pTFPlayer->m_Shared.SetDuckTimer( gpGlobals->curtime + TF_TIME_TO_DUCK );
+ if ( bInAir )
+ {
+ // Increment the number of times we have ducked in air.
+ int nCount = m_pTFPlayer->m_Shared.AirDuckedCount() + 1;
+ m_pTFPlayer->m_Shared.SetAirDucked( nCount );
+ }
+ }
+
+ // Try to unduck unless automovement is not allowed
+ // NOTE: When not onground, you can always unduck
+ if ( player->m_Local.m_bAllowAutoMovement || bInAir || player->m_Local.m_bDucking )
+ {
+ // We released the duck button, we aren't in "duck" and we are not in the air - start unduck transition.
+ if ( ( nButtonsReleased & IN_DUCK ) )
+ {
+ if ( bInDuck )
+ {
+ player->m_Local.m_flDucktime = GAMEMOVEMENT_DUCK_TIME;
+ }
+ else if ( player->m_Local.m_bDucking && !player->m_Local.m_bDucked )
+ {
+ // Invert time if release before fully ducked!!!
+ float unduckMilliseconds = 1000.0f * TIME_TO_UNDUCK;
+ float duckMilliseconds = 1000.0f * TIME_TO_DUCK;
+ float elapsedMilliseconds = GAMEMOVEMENT_DUCK_TIME - player->m_Local.m_flDucktime;
+
+ float fracDucked = elapsedMilliseconds / duckMilliseconds;
+ float remainingUnduckMilliseconds = fracDucked * unduckMilliseconds;
+
+ player->m_Local.m_flDucktime = GAMEMOVEMENT_DUCK_TIME - unduckMilliseconds + remainingUnduckMilliseconds;
+ }
+ }
+
+ // Check to see if we are capable of unducking.
+ if ( CanUnduck() )
+ {
+ // or unducking
+ if ( ( player->m_Local.m_bDucking || player->m_Local.m_bDucked ) )
+ {
+ float flDuckMilliseconds = MAX( 0.0f, GAMEMOVEMENT_DUCK_TIME - (float)player->m_Local.m_flDucktime );
+ float flDuckSeconds = flDuckMilliseconds * 0.001f;
+
+ // Finish ducking immediately if duck time is over or not on ground
+ if ( flDuckSeconds > TIME_TO_UNDUCK || bInAir )
+ {
+ FinishUnDuck();
+ }
+ else
+ {
+ // Calc parametric time
+ float flDuckFraction = SimpleSpline( 1.0f - ( flDuckSeconds / TIME_TO_UNDUCK ) );
+ SetDuckedEyeOffset( flDuckFraction );
+ player->m_Local.m_bDucking = true;
+ }
+ }
+ }
+ else
+ {
+ // Still under something where we can't unduck, so make sure we reset this timer so
+ // that we'll unduck once we exit the tunnel, etc.
+ if ( player->m_Local.m_flDucktime != GAMEMOVEMENT_DUCK_TIME )
+ {
+ SetDuckedEyeOffset(1.0f);
+ player->m_Local.m_flDucktime = GAMEMOVEMENT_DUCK_TIME;
+ player->m_Local.m_bDucked = true;
+ player->m_Local.m_bDucking = false;
+ player->AddFlag( FL_DUCKING );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Crop the speed of the player when ducking and on the ground.
+//-----------------------------------------------------------------------------
+void CTFGameMovement::HandleDuckingSpeedCrop( void )
+{
+ BaseClass::HandleDuckingSpeedCrop();
+
+ if ( m_iSpeedCropped & SPEED_CROPPED_DUCK )
+ {
+ if ( m_pTFPlayer->m_Shared.IsLoser() )
+ {
+ mv->m_flForwardMove *= 0;
+ mv->m_flSideMove *= 0;
+ mv->m_flUpMove *= 0;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: See if duck button is pressed and do the appropriate things
+//-----------------------------------------------------------------------------
+void CTFGameMovement::Duck( void )
+{
+ // Check duck overrides.
+ DuckOverrides();
+
+ // Calculate the button state.
+ int buttonsChanged = ( mv->m_nOldButtons ^ mv->m_nButtons ); // These buttons have changed this frame
+ int buttonsPressed = buttonsChanged & mv->m_nButtons; // The changed ones still down are "pressed"
+ int buttonsReleased = buttonsChanged & mv->m_nOldButtons; // The changed ones which were previously down are "released"
+ if ( mv->m_nButtons & IN_DUCK )
+ {
+ mv->m_nOldButtons |= IN_DUCK;
+ }
+ else
+ {
+ mv->m_nOldButtons &= ~IN_DUCK;
+ }
+
+ // Handle death.
+ if ( IsDead() )
+ {
+ // Reset view offset when dead
+ Vector vecStandViewOffset = GetPlayerViewOffset( false );
+ Vector vecOffset = player->GetViewOffset();
+ if ( vecOffset.z != vecStandViewOffset.z )
+ {
+ vecOffset.z = vecStandViewOffset.z;
+ player->SetViewOffset( vecOffset );
+ }
+
+ return;
+ }
+
+ // Slow down ducked players.
+ HandleDuckingSpeedCrop();
+
+ // If the player is holding down the duck button, the player is in duck transition, ducking, or duck-jumping.
+ bool bFirstTimePredicted = true; // Assumes we never rerun commands on the server.
+#ifdef CLIENT_DLL
+ bFirstTimePredicted = prediction->IsFirstTimePredicted();
+#endif
+
+ bool bInDuck = ( player->GetFlags() & FL_DUCKING ) ? true : false;
+ if ( ( mv->m_nButtons & IN_DUCK ) || player->m_Local.m_bDucking || bInDuck )
+ {
+ if ( ( mv->m_nButtons & IN_DUCK ) )
+ {
+ // DUCK
+ OnDuck( buttonsPressed );
+ }
+ else
+ {
+ // UNDUCK (or attempt to...)
+ OnUnDuck( buttonsReleased );
+ }
+ }
+ // HACK: (jimd 5/25/2006) we have a reoccuring bug (#50063 in Tracker) where the player's
+ // view height gets left at the ducked height while the player is standing, but we haven't
+ // been able to repro it to find the cause. It may be fixed now due to a change I'm
+ // also making in UpdateDuckJumpEyeOffset but just in case, this code will sense the
+ // problem and restore the eye to the proper position. It doesn't smooth the transition,
+ // but it is preferable to leaving the player's view too low.
+ //
+ // If the player is still alive and not an observer, check to make sure that
+ // his view height is at the standing height.
+ else if ( bFirstTimePredicted && !IsDead() && !player->IsObserver() && !player->IsInAVehicle() && !( TFGameRules() && TFGameRules()->ShowMatchSummary() ) )
+ {
+ float flOffsetDelta = player->GetViewOffset().z - GetPlayerViewOffset( false ).z;
+ if ( ( fabs( flOffsetDelta ) > 0.1 ) )
+ {
+ // we should rarely ever get here, so assert so a coder knows when it happens
+ AssertMsg2( 0, "Restoring player view height at %i %0.3f\n", gpGlobals->tickcount, gpGlobals->curtime );
+ DevMsg( 1, "Restoring player view height at %i %0.3f. Delta: %f.\n", gpGlobals->tickcount, gpGlobals->curtime, flOffsetDelta );
+
+ // set the eye height to the non-ducked height
+ SetDuckedEyeOffset(0.0f);
+ }
+ }
+
+ if ( tf_duck_debug_spew.GetBool() )
+ {
+#ifdef GAME_DLL
+ engine->Con_NPrintf( 0, "SERVER" );
+ engine->Con_NPrintf( 1, "m_flDucktime %3.2f", player->m_Local.m_flDucktime.Get() );
+ engine->Con_NPrintf( 2, "m_flDuckJumpTime %3.2f", player->m_Local.m_flDuckJumpTime.Get() );
+ engine->Con_NPrintf( 3, "m_bDucked %d", player->m_Local.m_bDucked.Get() );
+ engine->Con_NPrintf( 4, "m_bDucking %d", player->m_Local.m_bDucking.Get() );
+ engine->Con_NPrintf( 5, "m_bInDuckJump %d", player->m_Local.m_bInDuckJump.Get() );
+ engine->Con_NPrintf( 6, "viewoffset %3.2f, %3.2f, %3.2f", player->GetViewOffset().x, player->GetViewOffset().y, player->GetViewOffset().z );
+ engine->Con_NPrintf( 7, "IN_DUCK %d", mv->m_nButtons & IN_DUCK );
+ engine->Con_NPrintf( 8, "GetDuckTimer %3.2f", Max( 0.f, m_pTFPlayer->m_Shared.GetDuckTimer() - gpGlobals->curtime ) );
+#else
+ engine->Con_NPrintf( 10 + 0, "CLIENT" );
+ engine->Con_NPrintf( 10 + 1, "m_flDucktime %3.2f", player->m_Local.m_flDucktime );
+ engine->Con_NPrintf( 10 + 2, "m_flDuckJumpTime %3.2f", player->m_Local.m_flDuckJumpTime );
+ engine->Con_NPrintf( 10 + 3, "m_bDucked %d", player->m_Local.m_bDucked );
+ engine->Con_NPrintf( 10 + 4, "m_bDucking %d", player->m_Local.m_bDucking );
+ engine->Con_NPrintf( 10 + 5, "m_bInDuckJump %d", player->m_Local.m_bInDuckJump );
+ engine->Con_NPrintf( 10 + 6, "viewoffset %3.2f, %3.2f, %3.2f", player->GetViewOffset().x, player->GetViewOffset().y, player->GetViewOffset().z );
+ engine->Con_NPrintf( 10 + 7, "IN_DUCK %d", mv->m_nButtons & IN_DUCK );
+ engine->Con_NPrintf( 10 + 8, "GetDuckTimer %3.2f", Max( 0.f, m_pTFPlayer->m_Shared.GetDuckTimer() - gpGlobals->curtime ) );
+#endif
+ }
+}
+
+#ifdef STAGING_ONLY
+//-----------------------------------------------------------------------------
+// Purpose: See if the player's double tapped movement keys
+//-----------------------------------------------------------------------------
+void CTFGameMovement::CheckForDoubleTap( void )
+{
+ float flMaxDoubleTapTimeDelta = tf_movement_doubletap_window.GetFloat();
+
+ static const int aMoveType[4] =
+ {
+ IN_MOVELEFT,
+ IN_MOVERIGHT,
+ IN_FORWARD,
+ IN_BACK,
+ // Add movetypes here
+ };
+
+ for ( int i = 0; i < ARRAYSIZE( aMoveType ); ++i )
+ {
+ // Record when they let go of the key
+ if ( ( mv->m_nOldButtons & aMoveType[i] ) && !( mv->m_nButtons & aMoveType[i] ) )
+ {
+ int index = m_MoveKeyDownTimes.Find( aMoveType[i] );
+ if ( index != m_MoveKeyDownTimes.InvalidIndex() )
+ {
+ m_MoveKeyDownTimes[index] = gpGlobals->curtime;
+ }
+ else
+ {
+ // Init
+ m_MoveKeyDownTimes.Insert( aMoveType[i], gpGlobals->curtime );
+ }
+ }
+ // If the button is down now, and wasn't before...
+ else if ( ( mv->m_nButtons & aMoveType[i] ) && !( mv->m_nOldButtons & aMoveType[i] ) )
+ {
+ int index = m_MoveKeyDownTimes.Find( aMoveType[i] );
+ if ( index != m_MoveKeyDownTimes.InvalidIndex() )
+ {
+ // ...check the time delta - if it's within range, consider it a double-tap.
+ if ( gpGlobals->curtime - m_MoveKeyDownTimes[index] <= flMaxDoubleTapTimeDelta )
+ {
+ OnDoubleTapped( aMoveType[i] );
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: See if the player's double tapped movement keys
+//-----------------------------------------------------------------------------
+void CTFGameMovement::OnDoubleTapped( int nKey )
+{
+ int iTeleportMove = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pTFPlayer, iTeleportMove, ability_doubletap_teleport );
+ if ( iTeleportMove )
+ {
+ Vector vecDir, vecForward, vecRight;
+ AngleVectors( m_pTFPlayer->GetAbsAngles(), &vecForward, &vecRight, NULL );
+
+ if ( nKey == IN_MOVELEFT )
+ {
+ vecRight.Negate();
+ TeleportMove( vecRight, 192.f );
+ }
+ else if ( nKey == IN_MOVERIGHT )
+ {
+ TeleportMove( vecRight, 192.f );
+ }
+ else if ( nKey == IN_FORWARD )
+ {
+ TeleportMove( vecForward, 192.f );
+ }
+ else if ( nKey == IN_BACK )
+ {
+ vecForward.Negate();
+ TeleportMove( vecForward, 192.f );
+ }
+ }
+
+ // DevMsg( "Double Tap! (%i)\n", nKey );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGameMovement::TeleportMove( Vector &vecDirection, float flDist )
+{
+ if ( m_flNextDoubleTapTeleportTime > gpGlobals->curtime )
+ return;
+
+ trace_t result;
+ CTraceFilterIgnoreTeammates traceFilter( m_pTFPlayer, COLLISION_GROUP_PLAYER_MOVEMENT, m_pTFPlayer->GetTeamNumber() );
+ unsigned int nMask = m_pTFPlayer->GetTeamNumber() == TF_TEAM_RED ? CONTENTS_BLUETEAM : CONTENTS_REDTEAM;
+ nMask |= MASK_PLAYERSOLID;
+
+ // Try full distance
+ Vector vecPos = mv->GetAbsOrigin() + vecDirection * flDist;
+ UTIL_TraceHull( mv->GetAbsOrigin(), vecPos, VEC_HULL_MIN_SCALED( m_pTFPlayer ), VEC_HULL_MAX_SCALED( m_pTFPlayer ), nMask, &traceFilter, &result );
+ if ( result.DidHit() )
+ {
+ if ( result.fraction <= 0.2f )
+ return;
+
+ vecPos = mv->GetAbsOrigin() + ( ( vecPos - mv->GetAbsOrigin() ) * result.fraction );
+ // NDebugOverlay::SweptBox( mv->GetAbsOrigin(), vecPos, VEC_HULL_MIN_SCALED( m_pTFPlayer ), VEC_HULL_MAX_SCALED( m_pTFPlayer ), m_pTFPlayer->GetAbsAngles(), 255, 0, 0, 40, 5.f );
+ }
+
+ // Go there
+ mv->SetAbsOrigin( vecPos );
+
+#ifdef GAME_DLL
+ // Screen flash
+ color32 fadeColor = { 255, 255, 255, 50 };
+ UTIL_ScreenFade( m_pTFPlayer, fadeColor, 0.25f, 0.4f, FFADE_IN );
+
+ if ( TFGameRules() )
+ {
+ TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_TELEPORT, ( m_pTFPlayer->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED );
+ }
+#endif // GAME_DLL
+
+ // Cooldown
+ m_flNextDoubleTapTeleportTime = gpGlobals->curtime + 2.f;
+}
+#endif // STAGING_ONLY