//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "gamemovement.h" #include "cs_gamerules.h" #include "in_buttons.h" #include "movevars_shared.h" #include "weapon_csbase.h" #ifdef CLIENT_DLL #include "c_cs_player.h" #else #include "cs_player.h" #include "KeyValues.h" #endif #define STAMINA_MAX 100.0 #define STAMINA_COST_JUMP 25.0 #define STAMINA_COST_FALL 20.0 #define STAMINA_RECOVER_RATE 19.0 extern bool g_bMovementOptimizations; ConVar sv_timebetweenducks( "sv_timebetweenducks", "0", FCVAR_REPLICATED, "Minimum time before recognizing consecutive duck key", true, 0.0, true, 2.0 ); ConVar sv_enableboost( "sv_enableboost", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Allow boost exploits"); class CCSGameMovement : public CGameMovement { public: DECLARE_CLASS( CCSGameMovement, CGameMovement ); CCSGameMovement(); virtual void ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMove ); virtual bool CanAccelerate(); virtual bool CheckJumpButton( void ); virtual void PreventBunnyJumping( void ); virtual void ReduceTimers( void ); virtual void WalkMove( void ); virtual void AirMove( void ); virtual bool LadderMove( void ); virtual void DecayPunchAngle( void ); virtual void CheckParameters( void ); // allow overridden versions to respond to jumping virtual void OnJump( float fImpulse ); virtual void OnLand( float fVelocity ); // Ducking virtual void Duck( void ); virtual void FinishUnDuck( void ); virtual void FinishDuck( void ); virtual bool CanUnduck(); virtual void HandleDuckingSpeedCrop(); virtual bool OnLadder( trace_t &trace ); virtual float LadderDistance( void ) const { if ( player->GetMoveType() == MOVETYPE_LADDER ) return 10.0f; return 2.0f; } virtual unsigned int LadderMask( void ) const { return MASK_PLAYERSOLID & ( ~CONTENTS_PLAYERCLIP ); } virtual float ClimbSpeed( void ) const; virtual float LadderLateralMultiplier( void ) const; virtual void TryTouchGround( const Vector& start, const Vector& end, const Vector& mins, const Vector& maxs, unsigned int fMask, int collisionGroup, trace_t& pm ); protected: virtual void PlayerMove(); void CheckForLadders( bool wasOnGround ); virtual unsigned int PlayerSolidMask( bool brushOnly = false ); ///< returns the solid mask for the given player, so bots can have a more-restrictive set float m_fTimeLastUnducked; public: CCSPlayer *m_pCSPlayer; }; //----------------------------------------------------------------------------- // Purpose: used by the TryTouchGround function to exclude non-standables from // consideration //----------------------------------------------------------------------------- bool CheckForStandable( IHandleEntity *pHandleEntity, int contentsMask ) { CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); if ( !pEntity ) return false; return ( pEntity->IsPlayer() && pEntity->GetGroundEntity() != NULL ) || pEntity->IsStandable(); } // Expose our interface. static CCSGameMovement g_GameMovement; IGameMovement *g_pGameMovement = ( IGameMovement * )&g_GameMovement; EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CGameMovement, IGameMovement,INTERFACENAME_GAMEMOVEMENT, g_GameMovement ); // ---------------------------------------------------------------------------------------- // // CCSGameMovement. // ---------------------------------------------------------------------------------------- // CCSGameMovement::CCSGameMovement() { m_fTimeLastUnducked = 0.0f; } //----------------------------------------------------------------------------- // Purpose: Allow bots etc to use slightly different solid masks //----------------------------------------------------------------------------- unsigned int CCSGameMovement::PlayerSolidMask( bool brushOnly ) { bool isBot = !player || player->IsBot(); if ( brushOnly ) { if ( isBot ) { return MASK_PLAYERSOLID_BRUSHONLY | CONTENTS_MONSTERCLIP; } else { return MASK_PLAYERSOLID_BRUSHONLY; } } else { if ( isBot ) { return MASK_PLAYERSOLID | CONTENTS_MONSTERCLIP; } else { return MASK_PLAYERSOLID; } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CCSGameMovement::CheckParameters( void ) { QAngle v_angle; // maintaining auto-duck during jumps if ( m_pCSPlayer->m_duckUntilOnGround ) { if ( !player->GetGroundEntity() ) { if ( mv->m_nButtons & IN_DUCK ) { m_pCSPlayer->m_duckUntilOnGround = false; // player hit +duck, so cancel our auto duck } else { mv->m_nButtons |= IN_DUCK; } } } // it would be nice to put this into the player->GetPlayerMaxSpeed() method, but // this flag is only stored in the move! if ( mv->m_nButtons & IN_SPEED ) { mv->m_flMaxSpeed *= CS_PLAYER_SPEED_WALK_MODIFIER; } if ( player->GetMoveType() != MOVETYPE_ISOMETRIC && player->GetMoveType() != MOVETYPE_NOCLIP && player->GetMoveType() != MOVETYPE_OBSERVER ) { float spd; spd = ( mv->m_flForwardMove * mv->m_flForwardMove ) + ( mv->m_flSideMove * mv->m_flSideMove ) + ( mv->m_flUpMove * mv->m_flUpMove ); // Slow down by the speed factor float flSpeedFactor = 1.0f; if (player->m_pSurfaceData) { flSpeedFactor = player->m_pSurfaceData->game.maxSpeedFactor; } // If we have a constraint, slow down because of that too. float flConstraintSpeedFactor = ComputeConstraintSpeedFactor(); if (flConstraintSpeedFactor < flSpeedFactor) flSpeedFactor = flConstraintSpeedFactor; // Take the player's velocity modifier into account if ( FBitSet( m_pCSPlayer->GetFlags(), FL_ONGROUND ) ) { flSpeedFactor *= m_pCSPlayer->m_flVelocityModifier; } mv->m_flMaxSpeed *= flSpeedFactor; if ( g_bMovementOptimizations ) { // Same thing but only do the sqrt if we have to. if ( ( spd != 0.0 ) && ( spd > mv->m_flMaxSpeed*mv->m_flMaxSpeed ) ) { float fRatio = mv->m_flMaxSpeed / sqrt( spd ); mv->m_flForwardMove *= fRatio; mv->m_flSideMove *= fRatio; mv->m_flUpMove *= fRatio; } } else { spd = sqrt( spd ); if ( ( spd != 0.0 ) && ( spd > mv->m_flMaxSpeed ) ) { float fRatio = mv->m_flMaxSpeed / spd; mv->m_flForwardMove *= fRatio; mv->m_flSideMove *= fRatio; mv->m_flUpMove *= fRatio; } } } if ( player->GetFlags() & FL_FROZEN || player->GetFlags() & FL_ONTRAIN || IsDead() ) { mv->m_flForwardMove = 0; mv->m_flSideMove = 0; mv->m_flUpMove = 0; } DecayPunchAngle(); // Take angles from command. if ( !IsDead() ) { v_angle = mv->m_vecAngles; v_angle = v_angle + player->m_Local.m_vecPunchAngle; // Now adjust roll angle if ( player->GetMoveType() != MOVETYPE_ISOMETRIC && player->GetMoveType() != MOVETYPE_NOCLIP ) { mv->m_vecAngles[ROLL] = CalcRoll( v_angle, mv->m_vecVelocity, sv_rollangle.GetFloat(), sv_rollspeed.GetFloat() ); } else { mv->m_vecAngles[ROLL] = 0.0; // v_angle[ ROLL ]; } mv->m_vecAngles[PITCH] = v_angle[PITCH]; mv->m_vecAngles[YAW] = v_angle[YAW]; } else { mv->m_vecAngles = mv->m_vecOldAngles; } // Set dead player view_offset if ( IsDead() ) { player->SetViewOffset( VEC_DEAD_VIEWHEIGHT ); } // Adjust client view angles to match values used on server. if ( mv->m_vecAngles[YAW] > 180.0f ) { mv->m_vecAngles[YAW] -= 360.0f; } // If we're standing on a player, then force them off. if ( !player->IsObserver() && ( player->GetMoveType() != MOVETYPE_LADDER ) ) { int nLevels = 0; CBaseEntity *pCurGround = player->GetGroundEntity(); while ( pCurGround && pCurGround->IsPlayer() && nLevels < 1000 ) { pCurGround = pCurGround->GetGroundEntity(); ++nLevels; } if ( nLevels == 1000 ) Warning( "BUG: CCSGameMovement::CheckParameters - too many stacking levels.\n" ); // If they're stacked too many levels deep, slide them off. if ( nLevels > 1 ) { mv->m_flForwardMove = mv->m_flMaxSpeed * 3; mv->m_flSideMove = 0; mv->m_nButtons = 0; mv->m_nImpulseCommand = 0; } } } void CCSGameMovement::ProcessMovement( CBasePlayer *pBasePlayer, CMoveData *pMove ) { m_pCSPlayer = ToCSPlayer( pBasePlayer ); Assert( m_pCSPlayer ); BaseClass::ProcessMovement( pBasePlayer, pMove ); } bool CCSGameMovement::CanAccelerate() { // Only allow the player to accelerate when in certain states. CSPlayerState curState = m_pCSPlayer->State_Get(); if ( curState == STATE_ACTIVE ) { return player->GetWaterJumpTime() == 0; } else if ( player->IsObserver() ) { return true; } else { return false; } } void CCSGameMovement::PlayerMove() { if ( !m_pCSPlayer->CanMove() ) { mv->m_flForwardMove = 0; mv->m_flSideMove = 0; mv->m_flUpMove = 0; mv->m_nButtons &= ~(IN_JUMP | IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT); } BaseClass::PlayerMove(); if ( FBitSet( m_pCSPlayer->GetFlags(), FL_ONGROUND ) ) { if ( m_pCSPlayer->m_flVelocityModifier < 1.0 ) { m_pCSPlayer->m_flVelocityModifier += gpGlobals->frametime / 3.0f; } if ( m_pCSPlayer->m_flVelocityModifier > 1.0 ) m_pCSPlayer->m_flVelocityModifier = 1.0; } #if !defined(CLIENT_DLL) if ( m_pCSPlayer->IsAlive() ) { // Check if our eye height is too close to the ceiling and lower it. // This is needed because we have taller models with the old collision bounds. const float eyeClearance = 12.0f; // eye pos must be this far below the ceiling Vector offset = player->GetViewOffset(); Vector vHullMin = GetPlayerMins( player->m_Local.m_bDucked ); vHullMin.z = 0.0f; Vector vHullMax = GetPlayerMaxs( player->m_Local.m_bDucked ); Vector start = player->GetAbsOrigin(); start.z += vHullMax.z; Vector end = start; end.z += eyeClearance - vHullMax.z; end.z += player->m_Local.m_bDucked ? VEC_DUCK_VIEW_SCALED( player ).z : VEC_VIEW_SCALED( player ).z; vHullMax.z = 0.0f; Vector fudge( 1, 1, 0 ); vHullMin += fudge; vHullMax -= fudge; trace_t trace; Ray_t ray; ray.Init( start, end, vHullMin, vHullMax ); UTIL_TraceRay( ray, PlayerSolidMask(), mv->m_nPlayerHandle.Get(), COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); if ( trace.fraction < 1.0f ) { float est = start.z + trace.fraction * (end.z - start.z) - player->GetAbsOrigin().z - eyeClearance; if ( ( player->GetFlags() & FL_DUCKING ) == 0 && !player->m_Local.m_bDucking && !player->m_Local.m_bDucked ) { offset.z = est; } else { offset.z = MIN( est, offset.z ); } player->SetViewOffset( offset ); } else { if ( ( player->GetFlags() & FL_DUCKING ) == 0 && !player->m_Local.m_bDucking && !player->m_Local.m_bDucked ) { player->SetViewOffset( VEC_VIEW_SCALED( player ) ); } else if ( m_pCSPlayer->m_duckUntilOnGround ) { // Duck Hull, but we're in the air. Calculate where the view would be. Vector hullSizeNormal = VEC_HULL_MAX_SCALED( player ) - VEC_HULL_MIN_SCALED( player ); Vector hullSizeCrouch = VEC_DUCK_HULL_MAX_SCALED( player ) - VEC_DUCK_HULL_MIN_SCALED( player ); // We've got the duck hull, pulled up to the top of where the player should be Vector lowerClearance = hullSizeNormal - hullSizeCrouch; Vector duckEyeHeight = GetPlayerViewOffset( false ) - lowerClearance; player->SetViewOffset( duckEyeHeight ); } else if( player->m_Local.m_bDucked && !player->m_Local.m_bDucking ) { player->SetViewOffset( VEC_DUCK_VIEW ); } } } #endif } void CCSGameMovement::WalkMove( void ) { if ( m_pCSPlayer->m_flStamina > 0 ) { float flRatio; flRatio = ( STAMINA_MAX - ( ( m_pCSPlayer->m_flStamina / 1000.0 ) * STAMINA_RECOVER_RATE ) ) / STAMINA_MAX; // This Goldsrc code was run with variable timesteps and it had framerate dependencies. // People looking at Goldsrc for reference are usually // (these days) measuring the stoppage at 60fps or greater, so we need // to account for the fact that Goldsrc was applying more stopping power // since it applied the slowdown across more frames. float flReferenceFrametime = 1.0f / 70.0f; float flFrametimeRatio = gpGlobals->frametime / flReferenceFrametime; flRatio = pow( flRatio, flFrametimeRatio ); mv->m_vecVelocity.x *= flRatio; mv->m_vecVelocity.y *= flRatio; } BaseClass::WalkMove(); CheckForLadders( player->GetGroundEntity() != NULL ); } //------------------------------------------------------------------------------------------------------------------------------- void CCSGameMovement::AirMove( void ) { BaseClass::AirMove(); CheckForLadders( false ); } //------------------------------------------------------------------------------------------------------------------------------- bool CCSGameMovement::OnLadder( trace_t &trace ) { if ( trace.plane.normal.z == 1.0f ) return false; return BaseClass::OnLadder( trace ); } //------------------------------------------------------------------------------------------------------------------------------- bool CCSGameMovement::LadderMove( void ) { bool isOnLadder = BaseClass::LadderMove(); if ( isOnLadder && m_pCSPlayer ) { m_pCSPlayer->SurpressLadderChecks( mv->GetAbsOrigin(), m_pCSPlayer->m_vecLadderNormal ); } return isOnLadder; } //------------------------------------------------------------------------------------------------------------------------------- /** * In CS, crouching up ladders goes slowly and doesn't make a sound. */ float CCSGameMovement::ClimbSpeed( void ) const { if ( mv->m_nButtons & IN_DUCK ) { return BaseClass::ClimbSpeed() * CS_PLAYER_SPEED_CLIMB_MODIFIER; } else { return BaseClass::ClimbSpeed(); } } //------------------------------------------------------------------------------------------------------------------------------- /** * In CS, strafing on ladders goes slowly. */ float CCSGameMovement::LadderLateralMultiplier( void ) const { if ( mv->m_nButtons & IN_DUCK ) { return 1.0f; } else { return 0.5f; } } //------------------------------------------------------------------------------------------------------------------------------- /** * Looks behind and beneath the player in the air, in case he skips out over the top of a ladder. If the * trace hits a ladder, the player is snapped to the ladder. */ void CCSGameMovement::CheckForLadders( bool wasOnGround ) { if ( !wasOnGround ) { // If we're higher than the last place we were on the ground, bail - obviously we're not dropping // past a ladder we might want to grab. if ( mv->GetAbsOrigin().z > m_pCSPlayer->m_lastStandingPos.z ) return; Vector dir = -m_pCSPlayer->m_lastStandingPos + mv->GetAbsOrigin(); if ( !dir.x && !dir.y ) { // If we're dropping straight down, we don't know which way to look for a ladder. Oh well. return; } dir.z = 0.0f; float dist = dir.NormalizeInPlace(); if ( dist > 64.0f ) { // Don't grab ladders too far behind us. return; } trace_t trace; TracePlayerBBox( mv->GetAbsOrigin(), m_pCSPlayer->m_lastStandingPos - dir*(5+dist), (PlayerSolidMask() & (~CONTENTS_PLAYERCLIP)), COLLISION_GROUP_PLAYER_MOVEMENT, trace ); if ( trace.fraction != 1.0f && OnLadder( trace ) && trace.plane.normal.z != 1.0f ) { if ( m_pCSPlayer->CanGrabLadder( trace.endpos, trace.plane.normal ) ) { player->SetMoveType( MOVETYPE_LADDER ); player->SetMoveCollide( MOVECOLLIDE_DEFAULT ); player->SetLadderNormal( trace.plane.normal ); mv->m_vecVelocity.Init(); // The ladder check ignored playerclips, to fix a bug exposed by de_train, where a clipbrush is // flush with a ladder. This causes the above tracehull to fail unless we ignore playerclips. // However, we have to check for playerclips before we snap to that pos, so we don't warp a // player into a clipbrush. TracePlayerBBox( mv->GetAbsOrigin(), m_pCSPlayer->m_lastStandingPos - dir*(5+dist), PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace ); mv->SetAbsOrigin( trace.endpos ); } } } else { m_pCSPlayer->m_lastStandingPos = mv->GetAbsOrigin(); } } void CCSGameMovement::ReduceTimers( void ) { float frame_msec = 1000.0f * gpGlobals->frametime; if ( m_pCSPlayer->m_flStamina > 0 ) { m_pCSPlayer->m_flStamina -= frame_msec; if ( m_pCSPlayer->m_flStamina < 0 ) { m_pCSPlayer->m_flStamina = 0; } } BaseClass::ReduceTimers(); } ConVar sv_enablebunnyhopping( "sv_enablebunnyhopping", "0", FCVAR_REPLICATED | FCVAR_NOTIFY ); // Only allow bunny jumping up to 1.1x server / player maxspeed setting #define BUNNYJUMP_MAX_SPEED_FACTOR 1.1f // taken from TF2 but changed BUNNYJUMP_MAX_SPEED_FACTOR from 1.1 to 1.0 void CCSGameMovement::PreventBunnyJumping() { // 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; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CCSGameMovement::CheckJumpButton( void ) { if (m_pCSPlayer->pl.deadflag) { mv->m_nOldButtons |= IN_JUMP ; // don't jump again until released return false; } // See if we are waterjumping. If so, decrement count and return. if (m_pCSPlayer->m_flWaterJumpTime) { m_pCSPlayer->m_flWaterJumpTime -= gpGlobals->frametime; if (m_pCSPlayer->m_flWaterJumpTime < 0) m_pCSPlayer->m_flWaterJumpTime = 0; return false; } // If we are in the water most of the way... if ( m_pCSPlayer->GetWaterLevel() >= 2 ) { // swimming, not jumping SetGroundEntity( NULL ); if(m_pCSPlayer->GetWaterType() == CONTENTS_WATER) // We move up a certain amount mv->m_vecVelocity[2] = 100; else if (m_pCSPlayer->GetWaterType() == CONTENTS_SLIME) mv->m_vecVelocity[2] = 80; // play swiming sound if ( m_pCSPlayer->m_flSwimSoundTime <= 0 ) { // Don't play sound again for 1 second m_pCSPlayer->m_flSwimSoundTime = 1000; PlaySwimSound(); } return false; } // No more effect if (m_pCSPlayer->GetGroundEntity() == NULL) { mv->m_nOldButtons |= IN_JUMP; return false; // in air, so no effect } if ( mv->m_nOldButtons & IN_JUMP ) return false; // don't pogo stick if ( !sv_enablebunnyhopping.GetBool() ) { PreventBunnyJumping(); } // In the air now. SetGroundEntity( NULL ); m_pCSPlayer->PlayStepSound( (Vector &)mv->GetAbsOrigin(), player->m_pSurfaceData, 1.0, true ); //MoveHelper()->PlayerSetAnimation( PLAYER_JUMP ); m_pCSPlayer->DoAnimationEvent( PLAYERANIMEVENT_JUMP ); float flGroundFactor = 1.0f; if (player->m_pSurfaceData) { flGroundFactor = player->m_pSurfaceData->game.jumpFactor; } // if we weren't ducking, bots and hostages do a crouchjump programatically if ( (!player || player->IsBot()) && !(mv->m_nButtons & IN_DUCK) ) { m_pCSPlayer->m_duckUntilOnGround = true; FinishDuck(); } // Acclerate upward // If we are ducking... float startz = mv->m_vecVelocity[2]; if ( m_pCSPlayer->m_duckUntilOnGround || ( m_pCSPlayer->m_Local.m_bDucking ) || ( m_pCSPlayer->GetFlags() & FL_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] = flGroundFactor * sqrt(2 * 800 * 57.0); // 2 * gravity * height } else { mv->m_vecVelocity[2] += flGroundFactor * sqrt(2 * 800 * 57.0); // 2 * gravity * height } if ( m_pCSPlayer->m_flStamina > 0 ) { float flRatio; flRatio = ( STAMINA_MAX - ( ( m_pCSPlayer->m_flStamina / 1000.0 ) * STAMINA_RECOVER_RATE ) ) / STAMINA_MAX; mv->m_vecVelocity[2] *= flRatio; } m_pCSPlayer->m_flStamina = ( STAMINA_COST_JUMP / STAMINA_RECOVER_RATE ) * 1000.0; FinishGravity(); mv->m_outWishVel.z += mv->m_vecVelocity[2] - startz; mv->m_outStepHeight += 0.1f; OnJump(mv->m_outWishVel.z); #ifndef CLIENT_DLL // allow bots to react IGameEvent * event = gameeventmanager->CreateEvent( "player_jump" ); if ( event ) { event->SetInt( "userid", m_pCSPlayer->GetUserID() ); gameeventmanager->FireEvent( event ); } #endif // Flag that we jumped. mv->m_nOldButtons |= IN_JUMP; // don't jump again until released return true; } void CCSGameMovement::DecayPunchAngle( void ) { float len; Vector vPunchAngle; vPunchAngle.x = m_pCSPlayer->m_Local.m_vecPunchAngle->x; vPunchAngle.y = m_pCSPlayer->m_Local.m_vecPunchAngle->y; vPunchAngle.z = m_pCSPlayer->m_Local.m_vecPunchAngle->z; len = VectorNormalize ( vPunchAngle ); len -= (10.0 + len * 0.5) * gpGlobals->frametime; len = MAX( len, 0.0 ); VectorScale ( vPunchAngle, len, vPunchAngle ); m_pCSPlayer->m_Local.m_vecPunchAngle.Set( 0, vPunchAngle.x ); m_pCSPlayer->m_Local.m_vecPunchAngle.Set( 1, vPunchAngle.y ); m_pCSPlayer->m_Local.m_vecPunchAngle.Set( 2, vPunchAngle.z ); } void CCSGameMovement::HandleDuckingSpeedCrop() { //============================================================================= // HPE_BEGIN: // [Forrest] //============================================================================= // Movement speed in free look camera mode is unaffected by ducking state. if ( player->GetObserverMode() == OBS_MODE_ROAMING ) return; //============================================================================= // HPE_END //============================================================================= if ( !( m_iSpeedCropped & SPEED_CROPPED_DUCK ) ) { if ( ( mv->m_nButtons & IN_DUCK ) || ( player->m_Local.m_bDucking ) || ( player->GetFlags() & FL_DUCKING ) ) { mv->m_flForwardMove *= CS_PLAYER_SPEED_DUCK_MODIFIER; mv->m_flSideMove *= CS_PLAYER_SPEED_DUCK_MODIFIER; mv->m_flUpMove *= CS_PLAYER_SPEED_DUCK_MODIFIER; m_iSpeedCropped |= SPEED_CROPPED_DUCK; } } } bool CCSGameMovement::CanUnduck() { trace_t trace; Vector newOrigin; VectorCopy( mv->GetAbsOrigin(), newOrigin ); if ( player->GetGroundEntity() != NULL ) { newOrigin += VEC_DUCK_HULL_MIN_SCALED( player ) - VEC_HULL_MIN_SCALED( player ); } else { // If in air an letting go of croush, make sure we can offset origin to make // up for uncrouching Vector hullSizeNormal = VEC_HULL_MAX_SCALED( player ) - VEC_HULL_MIN_SCALED( player ); Vector hullSizeCrouch = VEC_DUCK_HULL_MAX_SCALED( player ) - VEC_DUCK_HULL_MIN_SCALED( player ); newOrigin += -0.5f * ( hullSizeNormal - hullSizeCrouch ); } UTIL_TraceHull( mv->GetAbsOrigin(), newOrigin, VEC_HULL_MIN_SCALED( player ), VEC_HULL_MAX_SCALED( player ), PlayerSolidMask(), player, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); if ( trace.startsolid || ( trace.fraction != 1.0f ) ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: Stop ducking //----------------------------------------------------------------------------- void CCSGameMovement::FinishUnDuck( void ) { trace_t trace; Vector newOrigin; VectorCopy( mv->GetAbsOrigin(), newOrigin ); if ( player->GetGroundEntity() != NULL ) { newOrigin += VEC_DUCK_HULL_MIN_SCALED( player ) - VEC_HULL_MIN_SCALED( player ); } else { // If in air an letting go of croush, make sure we can offset origin to make // up for uncrouching Vector hullSizeNormal = VEC_HULL_MAX_SCALED( player ) - VEC_HULL_MIN_SCALED( player ); Vector hullSizeCrouch = VEC_DUCK_HULL_MAX_SCALED( player ) - VEC_DUCK_HULL_MIN_SCALED( player ); Vector viewDelta = -0.5f * ( hullSizeNormal - hullSizeCrouch ); VectorAdd( newOrigin, viewDelta, newOrigin ); } player->m_Local.m_bDucked = false; player->RemoveFlag( FL_DUCKING ); player->m_Local.m_bDucking = false; player->SetViewOffset( GetPlayerViewOffset( false ) ); player->m_Local.m_flDucktime = 0; mv->SetAbsOrigin( newOrigin ); // Recategorize position since ducking can change origin CategorizePosition(); } //----------------------------------------------------------------------------- // Purpose: Finish ducking //----------------------------------------------------------------------------- void CCSGameMovement::FinishDuck( void ) { Vector hullSizeNormal = VEC_HULL_MAX_SCALED( player ) - VEC_HULL_MIN_SCALED( player ); Vector hullSizeCrouch = VEC_DUCK_HULL_MAX_SCALED( player ) - VEC_DUCK_HULL_MIN_SCALED( player ); Vector viewDelta = 0.5f * ( hullSizeNormal - hullSizeCrouch ); player->SetViewOffset( GetPlayerViewOffset( true ) ); player->AddFlag( FL_DUCKING ); player->m_Local.m_bDucking = false; if ( !player->m_Local.m_bDucked ) { Vector org = mv->GetAbsOrigin(); if ( player->GetGroundEntity() != NULL ) { org -= VEC_DUCK_HULL_MIN_SCALED( player ) - VEC_HULL_MIN_SCALED( player ); } else { org += viewDelta; } mv->SetAbsOrigin( org ); player->m_Local.m_bDucked = true; } // See if we are stuck? FixPlayerCrouchStuck( true ); // Recategorize position since ducking can change origin CategorizePosition(); } //----------------------------------------------------------------------------- // Purpose: See if duck button is pressed and do the appropriate things //----------------------------------------------------------------------------- void CCSGameMovement::Duck( void ) { // Fix taken from zblock for rapid crouch/stand not showing stand on other clients if ( player->GetFlags() & FL_ONGROUND ) { // if prevent crouch if ( !( mv->m_nButtons & IN_DUCK ) && ( mv->m_nOldButtons & IN_DUCK ) ) { // Player has released crouch and moving to standing m_fTimeLastUnducked = gpGlobals->curtime; } else if ( ( mv->m_nButtons & IN_DUCK ) && !( mv->m_nOldButtons & IN_DUCK ) ) { // Crouch from standing if ( ( player->GetFlags() & FL_DUCKING ) && ( m_fTimeLastUnducked > (gpGlobals->curtime - sv_timebetweenducks.GetFloat() ) ) ) { // if the server thinks the player is still crouched // AND the time the player started to stand (from being ducked) was less than 300ms ago // prevent the player from ducking again mv->m_nButtons &= ~IN_DUCK; } } } 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" // Check to see if we are in the air. bool bInAir = player->GetGroundEntity() == NULL && player->GetMoveType() != MOVETYPE_LADDER; if ( mv->m_nButtons & IN_DUCK ) { mv->m_nOldButtons |= IN_DUCK; } else { mv->m_nOldButtons &= ~IN_DUCK; } if ( IsDead() ) { // Unduck if ( player->GetFlags() & FL_DUCKING ) { FinishUnDuck(); } return; } HandleDuckingSpeedCrop(); if ( m_pCSPlayer->m_duckUntilOnGround ) { if ( !bInAir ) { m_pCSPlayer->m_duckUntilOnGround = false; if ( CanUnduck() ) { FinishUnDuck(); } return; } else { if ( mv->m_vecVelocity.z > 0.0f ) return; // Check if we can un-duck. We want to unduck if we have space for the standing hull, and // if it is less than 2 inches off the ground. trace_t trace; Vector newOrigin; Vector groundCheck; VectorCopy( mv->GetAbsOrigin(), newOrigin ); Vector hullSizeNormal = VEC_HULL_MAX_SCALED( player ) - VEC_HULL_MIN_SCALED( player ); Vector hullSizeCrouch = VEC_DUCK_HULL_MAX_SCALED( player ) - VEC_DUCK_HULL_MIN_SCALED( player ); newOrigin -= ( hullSizeNormal - hullSizeCrouch ); groundCheck = newOrigin; groundCheck.z -= player->GetStepSize(); UTIL_TraceHull( newOrigin, groundCheck, VEC_HULL_MIN_SCALED( player ), VEC_HULL_MAX_SCALED( player ), PlayerSolidMask(), player, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); if ( trace.startsolid || trace.fraction == 1.0f ) return; // Can't even stand up, or there's no ground underneath us m_pCSPlayer->m_duckUntilOnGround = false; if ( CanUnduck() ) { FinishUnDuck(); } return; } } // Holding duck, in process of ducking or fully ducked? if ( ( mv->m_nButtons & IN_DUCK ) || ( player->m_Local.m_bDucking ) || ( player->GetFlags() & FL_DUCKING ) ) { if ( mv->m_nButtons & IN_DUCK ) { bool alreadyDucked = ( player->GetFlags() & FL_DUCKING ) ? true : false; if ( (buttonsPressed & IN_DUCK ) && !( player->GetFlags() & FL_DUCKING ) ) { // Use 1 second so super long jump will work player->m_Local.m_flDucktime = 1000; player->m_Local.m_bDucking = true; } float duckmilliseconds = MAX( 0.0f, 1000.0f - (float)player->m_Local.m_flDucktime ); float duckseconds = duckmilliseconds / 1000.0f; //time = MAX( 0.0, ( 1.0 - (float)player->m_Local.m_flDucktime / 1000.0 ) ); if ( player->m_Local.m_bDucking ) { if ( !( player->GetFlags() & FL_ANIMDUCKING ) ) player->AddFlag( FL_ANIMDUCKING ); // Finish ducking immediately if duck time is over or not on ground if ( ( duckseconds > TIME_TO_DUCK ) || ( player->GetGroundEntity() == NULL ) || alreadyDucked) { FinishDuck(); } else { // Calc parametric time float duckFraction = SimpleSpline( duckseconds / TIME_TO_DUCK ); SetDuckedEyeOffset( duckFraction ); } } } else { // Try to unduck unless automovement is not allowed // NOTE: When not onground, you can always unduck if ( player->m_Local.m_bAllowAutoMovement || player->GetGroundEntity() == NULL ) { if ( (buttonsReleased & IN_DUCK ) && ( player->GetFlags() & FL_DUCKING ) ) { // Use 1 second so super long jump will work player->m_Local.m_flDucktime = 1000; player->m_Local.m_bDucking = true; // or unducking } float duckmilliseconds = MAX( 0.0f, 1000.0f - (float)player->m_Local.m_flDucktime ); float duckseconds = duckmilliseconds / 1000.0f; if ( CanUnduck() ) { if ( player->m_Local.m_bDucking || player->m_Local.m_bDucked ) // or unducking { if ( player->GetFlags() & FL_ANIMDUCKING ) player->RemoveFlag( FL_ANIMDUCKING ); // Finish ducking immediately if duck time is over or not on ground if ( ( duckseconds > TIME_TO_UNDUCK ) || ( player->GetGroundEntity() == NULL ) ) { FinishUnDuck(); } else { // Calc parametric time float duckFraction = SimpleSpline( 1.0f - ( duckseconds / TIME_TO_UNDUCK ) ); SetDuckedEyeOffset( duckFraction ); } } } 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. player->m_Local.m_flDucktime = 1000; } } } } } void CCSGameMovement::OnJump( float fImpulse ) { m_pCSPlayer->OnJump( fImpulse ); } void CCSGameMovement::OnLand( float fVelocity ) { m_pCSPlayer->OnLand( fVelocity ); } //----------------------------------------------------------------------------- // Purpose: Essentially the same as TracePlayerBBox, but adds a callback to // exclude entities that are not standable (except for other players) //----------------------------------------------------------------------------- void CCSGameMovement::TryTouchGround( const Vector& start, const Vector& end, const Vector& mins, const Vector& maxs, unsigned int fMask, int collisionGroup, trace_t& pm ) { VPROF( "CCSGameMovement::TryTouchGround" ); Ray_t ray; ray.Init( start, end, mins, maxs ); ShouldHitFunc_t pStandingTestCallback = sv_enableboost.GetBool() ? NULL : CheckForStandable; UTIL_TraceRay( ray, fMask, mv->m_nPlayerHandle.Get(), collisionGroup, &pm, pStandingTestCallback ); }