< Summary

Information
Class: Trailblazer.Navigation.Motor.JumpLocomotion
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/Motor/Locomotion/JumpLocomotion.cs
Line coverage
100%
Covered lines: 84
Uncovered lines: 0
Coverable lines: 84
Total lines: 294
Line coverage: 100%
Branch coverage
100%
Covered branches: 16
Total branches: 16
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor()100%11100%
get_IsEnabled()100%11100%
set_IsEnabled(...)100%22100%
get_CanJump()100%22100%
RequireContext()100%22100%
get_DeltaTime()100%11100%
get_TotalTime()100%11100%
RegisterJump()100%22100%
StartCooldown()100%11100%
UpdateCooldown()100%44100%
ResetJumpCounter()100%11100%
BindContext(...)100%11100%
RecordData(...)100%44100%

File(s)

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/Motor/Locomotion/JumpLocomotion.cs

#LineLine coverage
 1//=======================================================================
 2// JumpLocomotion.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System;
 9using Chronicler;
 10using FixedMathSharp;
 11using Trailblazer.Support;
 12
 13namespace Trailblazer.Navigation.Motor;
 14
 15/// <summary>
 16/// Handles the scout’s jumping mechanics, including jump height, cooldown, and movement control while airborne.
 17/// </summary>
 18/// <remarks>
 19/// This locomotion component determines how high the scout can jump, how much control they retain mid-air,
 20/// and enforces a cooldown period between consecutive jumps.
 21/// </remarks>
 22public class JumpLocomotion : ILocomotion
 23{
 24    private TrailblazerWorldContext? _context;
 25
 26    #region Constants
 27
 28    /// <summary>
 29    /// The default base jump height, representing how high the scout jumps when pressing and immediately releasing the 
 30    /// </summary>
 131    public static readonly Fixed64 DefaultBaseJumpHeight = Fixed64.One;
 32
 33    /// <summary>
 34    /// The additional height added when the jump button is held longer.
 35    /// </summary>
 136    public static readonly Fixed64 DefaultExtraJumpHeight = (Fixed64)2f;
 37
 38    /// <summary>
 39    /// Determines how much the scout's jump direction is influenced by the slope of the surface they are on.
 40    /// </summary>
 141    public static readonly Fixed64 DefaultPerpendicularJumpAmount = Fixed64.Half;
 42
 43    /// <summary>
 44    /// Determines how much the scout's jump direction is influenced by steep slopes.
 45    /// </summary>
 146    public static readonly Fixed64 DefaultSteepPerpendicularJumpAmount = Fixed64.Half;
 47
 48    /// <summary>
 49    /// Determines how much movement input affects the scout while jumping.
 50    /// </summary>
 151    public static readonly Fixed64 DefaultJumpControlMultiplier = Fixed64.FromFraction(3, 8); // 75% control when jumpin
 52
 53    /// <summary>
 54    /// The default cooldown time after a jump before the scout can jump again.
 55    /// </summary>
 156    public static readonly Fixed64 DefaultCooldownTime = Fixed64.FromFraction(1, 5);
 57
 58    /// <summary>
 59    /// The default duration after jumping during which ground checks should be ignored.
 60    /// </summary>
 161    public static readonly Fixed64 DefaultAvoidGroundingTimer = Fixed64.FromFraction(1, 20);
 62
 63    #endregion
 64
 65    #region Configuration State
 66
 67    /// <summary>
 68    /// Determines whether jumping is enabled.
 69    /// If disabled, the scout will be unable to jump.
 70    /// </summary>
 115471    private bool _isEnabled = true;
 72
 73    /// <summary>
 74    /// Maximum number of consecutive jumps allowed (e.g., 2 = double jump).
 75    /// </summary>
 115476    public int MaxJumpCount = 1;
 77
 78    /// <summary>
 79    /// The cooldown time before another jump can be performed.
 80    /// </summary>
 115481    public Fixed64 CooldownTime = DefaultCooldownTime;
 82
 83    /// <summary>
 84    /// The duration after jumping where ground detection is temporarily disabled.
 85    /// </summary>
 115486    public Fixed64 AvoidGroundingTimer = DefaultAvoidGroundingTimer;
 87
 88    /// <summary>
 89    /// The base height the scout can jump when the button is pressed briefly.
 90    /// </summary>
 115491    public Fixed64 BaseJumpHeight = DefaultBaseJumpHeight;
 92
 93    /// <summary>
 94    /// Additional height gained when holding the jump button.
 95    /// </summary>
 115496    public Fixed64 ExtraJumpHeight = DefaultExtraJumpHeight;
 97
 98    /// <summary>
 99    /// Controls how much movement input affects the scout while jumping.
 100    /// Lower values reduce movement responsiveness.
 101    /// </summary>
 1154102    public Fixed64 JumpControlMultiplier = DefaultJumpControlMultiplier;
 103
 104    /// <summary>
 105    /// Controls how much the scout jumps out perpendicular to the surface on walkable terrain.
 106    /// A value of 0 means fully vertical, and 1 means fully perpendicular.
 107    /// </summary>
 1154108    public Fixed64 PerpendicularJumpAmount = DefaultPerpendicularJumpAmount;
 109
 110    /// <summary>
 111    /// Controls how much the scout jumps out perpendicular to the surface on steep terrain.
 112    /// A value of 0 means fully vertical, and 1 means fully perpendicular.
 113    /// </summary>
 1154114    public Fixed64 SteepPerpendicularJumpAmount = DefaultSteepPerpendicularJumpAmount;
 115
 116    #endregion
 117
 118    #region Transient State
 119
 120    /// <inheritdoc cref="ILocomotion.IsEnabled"/>
 121    public bool IsEnabled
 122    {
 2187123        get => _isEnabled;
 124        set
 125        {
 2126            _isEnabled = value;
 2127            if (!_isEnabled)
 1128                this.ClearTransientState();
 2129        }
 130    }
 131
 132    /// <summary>
 133    /// Indicates whether the scout is currently performing a jump.
 134    /// </summary>
 135    /// <remarks>
 136    /// This is true if the jump button was pressed and the scout is not grounded.
 137    /// </remarks>
 138    [Transient]
 139    public bool IsJumping { get; set; }
 140
 141    /// <summary>
 142    /// Indicates whether the scout is holding the jump button.
 143    /// </summary>
 144    [Transient]
 145    public bool IsHoldingJump { get; set; }
 146
 147    /// <summary>
 148    /// The simulation frame when the scout started jumping.
 149    /// </summary>
 150    [Transient]
 151    public Fixed64 JumpStartTime { get; set; }
 152
 153    /// <summary>
 154    /// The direction in which the scout jumped during the current frame.
 155    /// </summary>
 156    [Transient]
 157    public Vector3d FrameJumpDirection { get; set; }
 158
 159    /// <summary>
 160    /// The elapsed time in the cooldown state.
 161    /// </summary>
 162    [Transient]
 163    public Fixed64 CooldownTimer { get; private set; }
 164
 165    /// <summary>
 166    /// Indicates whether the scout is currently in a jump cooldown period.
 167    /// </summary>
 168    [Transient]
 169    public bool IsCoolingDown { get; private set; }
 170
 171    /// <summary>
 172    /// The current number of jumps performed since the last grounding.
 173    /// </summary>
 174    [Transient]
 175    public int JumpCount { get; private set; }
 176
 177    /// <summary>
 178    /// Returns true if more jumps are allowed.
 179    /// </summary>
 213180    public bool CanJump => JumpCount < MaxJumpCount && !IsCoolingDown;
 181
 182    private TrailblazerWorldContext RequireContext() =>
 151183        _context ?? throw new InvalidOperationException("JumpLocomotion requires an explicit TrailblazerWorldContext.");
 184
 102185    private Fixed64 DeltaTime => RequireContext().DeltaTime;
 186
 49187    private Fixed64 TotalTime => RequireContext().TotalTime;
 188
 189    #endregion
 190
 191    #region Methods
 192
 193    /// <summary>
 194    /// Increments the jump counter.
 195    /// </summary>
 196    public void RegisterJump()
 197    {
 49198        Fixed64 startTime = TotalTime;
 48199        JumpCount++;
 48200        IsJumping = true;
 48201        IsHoldingJump = true;
 48202        JumpStartTime = startTime;
 203
 204        // Start cooldown only if this was the last allowed jump
 48205        if (JumpCount >= MaxJumpCount)
 36206            StartCooldown();
 48207    }
 208
 209    /// <summary>
 210    /// Starts the jump cooldown period, preventing another jump until the cooldown expires.
 211    /// </summary>
 212    public void StartCooldown()
 213    {
 48214        IsCoolingDown = true;
 48215        CooldownTimer = Fixed64.Zero;
 48216    }
 217
 218    /// <summary>
 219    /// Updates the jump cooldown timer, resetting the jump state when the cooldown expires.
 220    /// </summary>
 221    public void UpdateCooldown()
 222    {
 103223        if (!IsCoolingDown)
 1224            return;
 102225        CooldownTimer += DeltaTime;
 102226        if (CooldownTimer >= CooldownTime)
 227        {
 12228            CooldownTimer = Fixed64.Zero;
 12229            IsCoolingDown = false;
 230        }
 102231    }
 232
 233    /// <summary>
 234    /// Resets jump state upon grounding.
 235    /// </summary>
 236    public void ResetJumpCounter()
 237    {
 8238        JumpCount = 0;
 8239        IsJumping = false;
 8240        IsHoldingJump = false;
 8241    }
 242
 243    internal void BindContext(TrailblazerWorldContext context)
 244    {
 1244245        TrailblazerWorldContext.ThrowIfUnusable(context);
 1244246        _context = context;
 1244247    }
 248
 249    #endregion
 250
 251    /// <inheritdoc />
 252    public void RecordData(IChronicler chronicler)
 253    {
 158254        RecordValues.Look(chronicler, ref _isEnabled, "IsEnabled", true);
 158255        RecordValues.Look(chronicler, ref MaxJumpCount, "MaxJumpCount", 1);
 158256        RecordValues.Look(chronicler, ref CooldownTime, "CooldownTime", DefaultCooldownTime);
 158257        RecordValues.Look(chronicler, ref AvoidGroundingTimer, "AvoidGroundingTimer", DefaultAvoidGroundingTimer);
 158258        RecordValues.Look(chronicler, ref BaseJumpHeight, "BaseJumpHeight", DefaultBaseJumpHeight);
 158259        RecordValues.Look(chronicler, ref ExtraJumpHeight, "ExtraJumpHeight", DefaultExtraJumpHeight);
 158260        RecordValues.Look(chronicler, ref JumpControlMultiplier, "JumpControlMultiplier", DefaultJumpControlMultiplier);
 158261        RecordValues.Look(chronicler, ref PerpendicularJumpAmount, "PerpendicularJumpAmount", DefaultPerpendicularJumpAm
 158262        RecordValues.Look(chronicler, ref SteepPerpendicularJumpAmount, "SteepPerpendicularJumpAmount", DefaultSteepPerp
 263
 158264        bool isJumping = IsJumping;
 158265        bool isHoldingJump = IsHoldingJump;
 158266        Fixed64 jumpStartTime = JumpStartTime;
 158267        Vector3d frameJumpDirection = FrameJumpDirection;
 158268        Fixed64 cooldownTimer = CooldownTimer;
 158269        bool isCoolingDown = IsCoolingDown;
 158270        int jumpCount = JumpCount;
 271
 158272        RecordValues.Look(chronicler, ref isJumping, "IsJumping", false);
 158273        RecordValues.Look(chronicler, ref isHoldingJump, "IsHoldingJump", false);
 158274        RecordValues.Look(chronicler, ref jumpStartTime, "JumpStartTime", Fixed64.Zero);
 158275        RecordValues.Look(chronicler, ref frameJumpDirection, "FrameJumpDirection", Vector3d.Zero);
 158276        RecordValues.Look(chronicler, ref cooldownTimer, "CooldownTimer", Fixed64.Zero);
 158277        RecordValues.Look(chronicler, ref isCoolingDown, "IsCoolingDown", false);
 158278        RecordValues.Look(chronicler, ref jumpCount, "JumpCount", 0);
 279
 158280        if (chronicler.Mode == SerializationMode.Loading)
 281        {
 74282            IsJumping = isJumping;
 74283            IsHoldingJump = isHoldingJump;
 74284            JumpStartTime = jumpStartTime;
 74285            FrameJumpDirection = frameJumpDirection;
 74286            CooldownTimer = cooldownTimer;
 74287            IsCoolingDown = isCoolingDown;
 74288            JumpCount = jumpCount;
 289
 74290            if (!_isEnabled)
 2291                this.ClearTransientState();
 292        }
 158293    }
 294}