//========= 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( 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