Duck Jump Fix

From Valve Developer Community
Jump to: navigation, search
English (en)Deutsch (de)русский (ru)
... Icon-Important.png

Introduction

This article will show you how to fix Valve's implementation of Duck Jumping. Valve's implementation (in the Orange Box Source SDK) moves the player's origin upwards in the positive z-axis, approximately 32 units in one frame. This causes a huge problem for both dealing with prediction, and for the hit box synchronization between the server and client. This also causes the player to appear to pop-up in the air!

To fix this issue we add an interpolation to the ducking and unducking while in the air. Since we have a short time to apply these interpolations, we speed up the rate of ducking while jumping by two times the normal ducking speed.

Requirements

  • Strong C++ background
  • Knowledge of the Source SDK
  • General knowledge of player movement

The Code

There is not much to the code, the only thing needed is to add two functions and modify two conditional statements. Below you will find a detailed breakdown of each change, so just copy and paste into the relevant sections if you wish to do so.

GameMovement.h

Add the following function declarations to CGameMovement in GameMovement.h (Located in : src\game\shared...), around (line 205):

virtual void	DoDuckJump( float flFraction );
virtual void	DoUnDuckJump( float flFraction );

GameMovement.cpp

First we define how much we want to scale the height difference between full duck and full standing for our duck jump. You might want to make this number higher or lower for your mod since we also modified the height of jumping (NOT part of this tutorial). In GameMovement.cpp (Located in the same folder as GameMovement.h), add this define right underneath the include files :

#define DUCKJUMP_SCALING  0.2f

Next, we fill out the functions that we declared in the header (read the comments for description). Just place these definitions somewhere, preferably near the other ducking functions for continuity.

void CGameMovement::DoDuckJump( float flFraction )
{
	if ( flFraction >= 1.0f )
	{
		// Since we accelerate the ducking time artificially say that we are ducking so we don't fly up in the air
		player->AddFlag( FL_DUCKING );
		player->m_Local.m_bDucked = true;
		player->m_Local.m_bDucking = false;

		// Force the view offset to be "fully ducked"
		player->SetViewOffset( GetPlayerViewOffset( true ) );
	}
	else
	{
		// Move our view down
		SetDuckedEyeOffset( flFraction );

		// Move our body up a fraction of the difference between fully crouched and fully standing
		Vector hullSizeNormal = VEC_HULL_MAX - VEC_HULL_MIN;
		Vector hullSizeCrouch = VEC_DUCK_HULL_MAX - VEC_DUCK_HULL_MIN;
		Vector viewDelta = ( hullSizeNormal - hullSizeCrouch ) * DUCKJUMP_SCALING * flFraction;
		Vector out;
		VectorAdd( mv->GetAbsOrigin(), viewDelta, out );
		mv->SetAbsOrigin( out );

		// See if we are stuck?
		FixPlayerCrouchStuck( true );

		// Recategorize position since ducking can change origin
		CategorizePosition();
	}
}

void CGameMovement::DoUnDuckJump( float flFraction )
{
	if ( flFraction <= 0.0f )
	{
		// Since we accelerate the ducking time artificially say that we are not ducking so we don't go through the floor
		player->m_Local.m_bDucked = false;
		player->RemoveFlag( FL_DUCKING );
		player->m_Local.m_bDucking  = false;
		player->m_Local.m_bInDuckJump  = false;

		// Set our view offset to fully standing
		player->SetViewOffset( GetPlayerViewOffset( false ) );
		player->m_Local.m_flDucktime = 0;
	}
	else
	{
		// Move our view up
		SetDuckedEyeOffset( flFraction );

		// Move our body down a fraction of the difference between fully crouched and fully standing
		Vector hullSizeNormal = VEC_HULL_MAX - VEC_HULL_MIN;
		Vector hullSizeCrouch = VEC_DUCK_HULL_MAX - VEC_DUCK_HULL_MIN;
		Vector viewDelta = ( hullSizeNormal - hullSizeCrouch ) * DUCKJUMP_SCALING * flFraction;
		viewDelta.Negate();
		Vector out;
		VectorAdd( mv->GetAbsOrigin(), viewDelta, out );

		mv->SetAbsOrigin( out );

		// See if we are stuck?
		FixPlayerCrouchStuck( true );

		// Recategorize position since ducking can change origin
		CategorizePosition();
	}
}

Finally, we setup the ducking function to utilize our new functions to interpolate the player through their duck. I can't give you exact line numbers since my gamemovement.cpp file is heavily hacked up for our mod.

Replace this block of code :

// The player is in duck transition and not duck-jumping.
if ( player->m_Local.m_bDucking && !bDuckJump && !bDuckJumpTime )
{
	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 );
	}
}

With this block of code :

// The player is in duck transition and not duck-jumping.
if ( player->m_Local.m_bDucking && !bDuckJump && !bDuckJumpTime )
{
	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 if ( bInAir )
	{
		// Speed up our duck transition by two times if we are duck jumping
		float flDuckFraction = clamp( SimpleSpline( flDuckSeconds / TIME_TO_DUCK )*2.0f, 0, 1.0f );
		DoDuckJump( flDuckFraction );
	}
	else
	{
		// Calc parametric time
		float flDuckFraction = SimpleSpline( flDuckSeconds / TIME_TO_DUCK );
		SetDuckedEyeOffset( flDuckFraction );
	}
}


And then, replace this block of code :

// 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 && !bDuckJump ) )
		{
			FinishUnDuck();
		}
		else
		{
			// Calc parametric time
			float flDuckFraction = SimpleSpline( 1.0f - ( flDuckSeconds / TIME_TO_UNDUCK ) );
			SetDuckedEyeOffset( flDuckFraction );
			player->m_Local.m_bDucking = true;
		}
	}
}

With this code :

// 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 if ( bInAir )
		{
			// Reverse our process
			float flDuckFraction = clamp( SimpleSpline( 1.0f - (flDuckSeconds / TIME_TO_UNDUCK) )*2.0f, 0, 1.0f );
			DoUnDuckJump( flDuckFraction );
		}
		else
		{
			// Calc parametric time
			float flDuckFraction = SimpleSpline( 1.0f - ( flDuckSeconds / TIME_TO_UNDUCK ) );
			SetDuckedEyeOffset( flDuckFraction );
			player->m_Local.m_bDucking = true;
		}
	}
}

That's it, now when you are jumping (bInAir == true) your view and position (and bounding box and hitboxes) will be modified by DoDuckJump/DoUnDuckJump and when you are on the ground your view will be modified by the normal SetDuckedEyeOffset()

Warning.pngWarning: Beta Testers reported that this duck-jump implementation causes you to lose velocity while running and duck-jumping. This is not a big deal if you are trying to eliminate bunny hopping from your mod, but if you wish to have bunny hopping or CSS like movement, this implementation will not work for you, if it's done without any modification.


Note.pngNote: One way to preserve the player's velocity would be to calculate the new origin and also adding in a vector movement based on the player's velocity (instead of just pure z movement).