Duck Jump Fix
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()