Summary
ACharacter::CheckJumpInput (Character.cpp:1591 in 5.8.3) reads:
const bool bDidJump = CanJump() && (CharacterCVars::UseLegacyDoJump)? CharacterMovement->DoJump(bClientUpdating) : CharacterMovement->DoJump(bClientUpdating, DeltaTime);
&& binds tighter than ?:, so this is (CanJump() && UseLegacyDoJump) ? DoJump(a) : DoJump(a, DeltaTime). CanJump() does not gate the call. It only picks a branch, and both branches call a DoJump. Two effects:
DoJump(bool, float)is called whenCanJump()is false.- With the default
p.UseLegacyDoJump 1, the deprecated one-argument overload runs when the jump is allowed and forwardsDeltaTime = 0. The two-argument overload gets the realDeltaTimeonly when the jump is refused.
What Type of Bug are you experiencing?
Gameplay
Steps to Reproduce
Measured in an automation test in a game module, using only engine classes plus two small subclasses. The movement component’s DoJump only logs and calls Super:
UCLASS() class UReproCMC : public UCharacterMovementComponent { GENERATED_BODY()
public:
virtual bool DoJump(bool bReplayingMoves, float DeltaTime) override {
const bool bCan = CharacterOwner && CharacterOwner->CanJump();
const bool bResult = Super::DoJump(bReplayingMoves, DeltaTime);
UE_LOG(LogTemp, Display, TEXT("DoJump DeltaTime=%.3f CanJump()=%d returned=%d"), DeltaTime, bCan, bResult);
return bResult; } };
UCLASS() class AReproCharacter : public ACharacter { GENERATED_BODY()
public:
AReproCharacter(const FObjectInitializer& OI) : Super(OI.SetDefaultSubobjectClass<UReproCMC>(ACharacter::CharacterMovementComponentName))
{ GetCharacterMovement()->GetNavAgentPropertiesRef().bCanCrouch = true; } };
- Spawn
AReproCharacteron a floor, possess it with a locally controlledAPlayerController, and let it settle on the ground. - Standing: call
Jump()and tick the movement component once at 0.05 s. - Crouched: call
Crouch(), tick, thenJump()and tick once at 0.05 s.CanJump()is false while crouched. - Repeat both with
p.UseLegacyDoJump 0.
Expected Result
When CanJump() is false, DoJump is not called, which is what CanJump() && reads as. The overload and DeltaTime do not depend on whether the jump is allowed.
Observed Result
(one 0.05 s tick each)
p.UseLegacyDoJump 1(default), standing:CanJump()true, DoJump called, DeltaTime 0.000, launched.p.UseLegacyDoJump 1, crouched:CanJump()false, DoJump called, DeltaTime 0.050, returned false.p.UseLegacyDoJump 0, standing: DoJump called, DeltaTime 0.050, launched.p.UseLegacyDoJump 0, crouched:CanJump()false, DoJump still called, DeltaTime 0.050, returned false.
Affects Versions
5.8
Platform(s)
Mac
Additional Notes
Low severity. The stock UCharacterMovementComponent::DoJump re-checks CanJump(), so a stock Character behaves correctly, and the doc comment says DoJump checks it. It matters for an override that acts before calling Super. I found it while overriding DoJump to stand a crouched character up before a jump: the override has to re-check CanJump() itself, because it runs on refused jumps too. Suggested fix: parenthesise as CanJump() && (UseLegacyDoJump ? DoJump(a) : DoJump(a, DeltaTime)). Verified in the 5.8.3 source and at runtime on macOS, Apple Silicon. I have not checked other versions.