diff options
Diffstat (limited to 'game/shared/tf/tf_gamemovement.cpp')
| -rw-r--r-- | game/shared/tf/tf_gamemovement.cpp | 3558 |
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 |