< Summary

Information
Class: Trailblazer.Navigation.Motor.LocomotionHandler
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/Motor/Locomotion/LocomotionHandler.cs
Line coverage
100%
Covered lines: 240
Uncovered lines: 0
Coverable lines: 240
Total lines: 623
Line coverage: 100%
Branch coverage
92%
Covered branches: 107
Total branches: 116
Branch coverage: 92.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%22100%
.ctor()100%11100%
get_Forces()100%11100%
get_Move()100%11100%
get_Platform()100%11100%
get_Jump()100%11100%
get_Fall()100%11100%
get_Slide()100%11100%
get_Water()100%11100%
get_Fly()100%11100%
get_Climb()100%11100%
Has(...)100%11100%
Has()100%11100%
TryGet(...)100%11100%
Require()100%22100%
Install(...)100%11100%
Replace(...)100%11100%
Remove()100%88100%
ApplyProfile(...)100%11100%
BindContext(...)100%11100%
ToProfile()100%11100%
ConfigureInstalledKinds(...)100%1010100%
RefreshInstalledKinds()100%1010100%
GetLocomotion(...)100%22100%
GetLocomotion(...)88.88%99100%
GetLocomotions()50%1010100%
SetLocomotion(...)100%22100%
TryResolveLocomotionSlot(...)100%1616100%
SetLocomotion(...)88.88%99100%
BindInstalledLocomotions()100%11100%
BindLocomotion(...)100%1010100%
ClearReplacedLocomotion(...)100%44100%
SyncTransientState(...)100%88100%
ClearTransientState()50%44100%
ClearAllTransientState()100%44100%
RecordData(...)100%44100%
RecordOptionalLocomotion(...)100%22100%

File(s)

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

#LineLine coverage
 1using Chronicler;
 2using SwiftCollections;
 3using System;
 4using System.Collections.Generic;
 5using System.Diagnostics.CodeAnalysis;
 6using System.Runtime.CompilerServices;
 7
 8namespace Trailblazer.Navigation.Motor;
 9
 10/// <summary>
 11/// Manages locomotion states and behaviors for the <see cref="NavMotor"/>.
 12/// </summary>
 13/// <remarks>
 14/// This class coordinates multiple locomotion types, ensuring that movement states are properly managed.
 15/// </remarks>
 16public class LocomotionHandler : IRecordable
 17{
 18    #region Nested Types
 19
 20    private enum LocomotionSlot
 21    {
 22        Move,
 23        Platform,
 24        Jump,
 25        Fall,
 26        Slide,
 27        Water,
 28        Fly,
 29        Climb
 30    }
 31
 32    #endregion
 33
 34    #region Fields
 35
 36    /// <summary>
 37    /// Determines whether the scout has control over movement input.
 38    /// </summary>
 74939    public bool IsInControl = true;
 40
 74941    private LocomotionForces _forces = new();
 42
 43    private MoveLocomotion _move = null!;
 44
 45    private FallLocomotion _fall = null!;
 46
 47    private PlatformLocomotion _platform = null!;
 48
 49    private JumpLocomotion? _jump;
 50
 51    private SlideLocomotion? _slide;
 52
 53    private WaterLocomotion? _water;
 54
 55    private FlyLocomotion? _fly;
 56
 57    private ClimbLocomotion? _climb;
 58
 59    private TrailblazerWorldContext? _context;
 60
 61    #endregion
 62
 63    #region Initialization
 64
 65    /// <summary>
 66    /// Initializes a new handler using the default locomotion profile.
 67    /// </summary>
 68    public LocomotionHandler()
 118269        : this(LocomotionProfile.CreateDefault()) { }
 70
 71    /// <summary>
 72    /// Initializes a new handler from a locomotion profile.
 73    /// </summary>
 74974    public LocomotionHandler(LocomotionProfile profile)
 75    {
 74976        ApplyProfile(profile ?? throw new ArgumentNullException(nameof(profile)));
 74877    }
 78
 79    #endregion
 80
 81    #region Properties
 82
 83    /// <summary>
 84    /// Gets the currently installed locomotion kinds.
 85    /// </summary>
 86    public LocomotionKind InstalledKinds { get; private set; } = LocomotionKind.All;
 87
 88    /// <inheritdoc cref="LocomotionForces"/>
 379489    public LocomotionForces Forces => _forces;
 90
 91    /// <summary>
 92    /// Handles general movement, including speed limits, acceleration, and velocity calculations.
 93    /// </summary>
 1210194    public MoveLocomotion Move => _move;
 95
 96    /// <summary>
 97    /// Manages movement when interacting with moving platforms or surfaces.
 98    /// </summary>
 99    /// <remarks>
 100    /// This locomotion maintains platform velocity tracking and movement transfer states.
 101    /// </remarks>
 13357102    public PlatformLocomotion Platform => _platform;
 103
 104    /// <summary>
 105    /// Controls the airborne state when a jump is executed successfully.
 106    /// </summary>
 107    /// <remarks>
 108    /// This locomotion governs jump height, cooldown timing, and jump force calculations.
 109    /// </remarks>
 19405110    public JumpLocomotion? Jump => _jump;
 111
 112    /// <summary>
 113    /// Handles the scout’s falling behavior when downward momentum is detected.
 114    /// </summary>
 115    /// <remarks>
 116    /// This locomotion tracks fall distance, applies landing impact logic, and determines if a scout is free-falling.
 117    /// </remarks>
 14296118    public FallLocomotion Fall => _fall;
 119
 120    /// <summary>
 121    /// Manages movement when sliding down steep surfaces.
 122    /// </summary>
 123    /// <remarks>
 124    /// This locomotion determines when the scout should slide and how much control it has over movement during the slid
 125    /// </remarks>
 4688126    public SlideLocomotion? Slide => _slide;
 127
 128    /// <summary>
 129    /// Handles movement when the scout is in water, including active swimming, buoyancy, and water resistance.
 130    /// </summary>
 131    /// <remarks>
 132    /// This locomotion tracks liquid-medium state such as swim speed, floating and sinking behavior,
 133    /// dive time, and breath management.
 134    /// </remarks>
 10282135    public WaterLocomotion? Water => _water;
 136
 137    /// <summary>
 138    /// Handles controlled flight while the scout is airborne.
 139    /// </summary>
 26286140    public FlyLocomotion? Fly => _fly;
 141
 142    /// <summary>
 143    /// Handles climb configuration and runtime attachment state.
 144    /// </summary>
 25489145    public ClimbLocomotion? Climb => _climb;
 146
 147    #endregion
 148
 149    #region Composition
 150
 151    /// <summary>
 152    /// Gets whether a built-in locomotion kind is installed.
 153    /// </summary>
 154    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 155    public bool Has(LocomotionKind kind)
 156    {
 2157        return (InstalledKinds & kind) == kind;
 158    }
 159
 160    /// <summary>
 161    /// Gets whether a built-in locomotion type is installed.
 162    /// </summary>
 163    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 164    public bool Has<T>() where T : class, ILocomotion
 165    {
 3166        return TryGet<T>(out _);
 167    }
 168
 169    /// <summary>
 170    /// Attempts to retrieve an installed locomotion by type.
 171    /// </summary>
 172    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 173    public bool TryGet<T>([NotNullWhen(true)] out T? locomotion) where T : class, ILocomotion
 174    {
 9175        locomotion = GetLocomotion(typeof(T)) as T;
 9176        return locomotion != null;
 177    }
 178
 179    /// <summary>
 180    /// Retrieves an installed locomotion by type or throws if it is not installed.
 181    /// </summary>
 182    public T Require<T>() where T : class, ILocomotion
 183    {
 3184        if (TryGet<T>(out T? locomotion))
 1185            return locomotion;
 186
 2187        throw new InvalidOperationException($"{typeof(T).Name} is not installed on this locomotion handler.");
 188    }
 189
 190    /// <summary>
 191    /// Installs or replaces a locomotion instance.
 192    /// </summary>
 193    public void Install<T>(T locomotion) where T : class, ILocomotion
 194    {
 3195        Replace(locomotion);
 3196    }
 197
 198    /// <summary>
 199    /// Replaces a locomotion instance with a new one of the same type.
 200    /// </summary>
 201    public void Replace<T>(T locomotion) where T : class, ILocomotion
 202    {
 13203        SwiftThrowHelper.ThrowIfNull(locomotion, nameof(locomotion));
 204
 12205        SetLocomotion(typeof(T), locomotion);
 11206        RefreshInstalledKinds();
 11207    }
 208
 209    /// <summary>
 210    /// Removes an optional locomotion from the handler.
 211    /// </summary>
 212    public bool Remove<T>() where T : class, ILocomotion
 213    {
 7214        Type type = typeof(T);
 7215        if (type == typeof(MoveLocomotion)
 7216            || type == typeof(PlatformLocomotion)
 7217            || type == typeof(FallLocomotion))
 218        {
 3219            return false;
 220        }
 221
 4222        ILocomotion? locomotion = GetLocomotion(type);
 4223        if (locomotion == null)
 1224            return false;
 225
 3226        locomotion.ClearTransientState();
 227
 3228        SetLocomotion(type, null);
 3229        RefreshInstalledKinds();
 3230        return true;
 231    }
 232
 233    /// <summary>
 234    /// Replaces the installed locomotions with a new profile.
 235    /// </summary>
 236    public void ApplyProfile(LocomotionProfile profile)
 237    {
 866238        SwiftThrowHelper.ThrowIfNull(profile, nameof(profile));
 239
 865240        ClearReplacedLocomotion(Move, profile.Move);
 865241        ClearReplacedLocomotion(Fall, profile.Fall);
 865242        ClearReplacedLocomotion(Platform, profile.Platform);
 865243        ClearReplacedLocomotion(Jump, profile.Jump);
 865244        ClearReplacedLocomotion(Slide, profile.Slide);
 865245        ClearReplacedLocomotion(Water, profile.Water);
 865246        ClearReplacedLocomotion(Fly, profile.Fly);
 865247        ClearReplacedLocomotion(Climb, profile.Climb);
 248
 865249        _move = profile.Move;
 865250        _fall = profile.Fall;
 865251        _platform = profile.Platform;
 865252        _jump = profile.Jump;
 865253        _slide = profile.Slide;
 865254        _water = profile.Water;
 865255        _fly = profile.Fly;
 865256        _climb = profile.Climb;
 257
 865258        BindInstalledLocomotions();
 865259        RefreshInstalledKinds();
 865260    }
 261
 262    internal void BindContext(TrailblazerWorldContext context)
 263    {
 779264        Trailblazer.Pathing.PathRequestContextResolver.ThrowIfUnusable(context);
 779265        _context = context;
 779266        BindInstalledLocomotions();
 779267    }
 268
 269    /// <summary>
 270    /// Creates a profile representing the handler's current locomotion composition.
 271    /// </summary>
 272    public LocomotionProfile ToProfile()
 273    {
 1274        return new LocomotionProfile(
 1275            Move,
 1276            Fall,
 1277            Platform,
 1278            Jump,
 1279            Slide,
 1280            Water,
 1281            Fly,
 1282            Climb);
 283    }
 284
 285    internal void ConfigureInstalledKinds(LocomotionKind kinds)
 286    {
 56287        LocomotionKind normalizedKinds = kinds | LocomotionKind.Core;
 56288        var builder = new LocomotionProfileBuilder(includeOptionalLocomotions: false);
 289
 56290        if ((normalizedKinds & LocomotionKind.Jump) != 0)
 50291            builder.WithJump();
 292
 56293        if ((normalizedKinds & LocomotionKind.Slide) != 0)
 50294            builder.WithSlide();
 295
 56296        if ((normalizedKinds & LocomotionKind.Water) != 0)
 50297            builder.WithWater();
 298
 56299        if ((normalizedKinds & LocomotionKind.Fly) != 0)
 51300            builder.WithFly();
 301
 56302        if ((normalizedKinds & LocomotionKind.Climb) != 0)
 51303            builder.WithClimb();
 304
 56305        ApplyProfile(builder.Build());
 56306    }
 307
 308    private void RefreshInstalledKinds()
 309    {
 879310        InstalledKinds = LocomotionKind.Core;
 311
 879312        if (Jump != null)
 841313            InstalledKinds |= LocomotionKind.Jump;
 314
 879315        if (Slide != null)
 834316            InstalledKinds |= LocomotionKind.Slide;
 317
 879318        if (Water != null)
 836319            InstalledKinds |= LocomotionKind.Water;
 320
 879321        if (Fly != null)
 836322            InstalledKinds |= LocomotionKind.Fly;
 323
 879324        if (Climb != null)
 835325            InstalledKinds |= LocomotionKind.Climb;
 879326    }
 327
 328    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 329    private ILocomotion? GetLocomotion(Type type)
 330    {
 57331        if (!TryResolveLocomotionSlot(type, out LocomotionSlot slot))
 3332            return null;
 333
 54334        return GetLocomotion(slot);
 335    }
 336
 337    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 338    private ILocomotion? GetLocomotion(LocomotionSlot slot)
 339    {
 340#pragma warning disable CS8524 // LocomotionSlot is only produced by TryResolveLocomotionSlot over known built-in locomo
 54341        return slot switch
 54342        {
 5343            LocomotionSlot.Move => Move,
 1344            LocomotionSlot.Platform => Platform,
 5345            LocomotionSlot.Jump => Jump,
 19346            LocomotionSlot.Fall => Fall,
 2347            LocomotionSlot.Slide => Slide,
 8348            LocomotionSlot.Water => Water,
 1349            LocomotionSlot.Fly => Fly,
 13350            LocomotionSlot.Climb => Climb
 54351        };
 352#pragma warning restore CS8524
 353    }
 354
 355    /// <summary>
 356    /// Gets and enumerates all locomotion instances in the handler.
 357    /// </summary>
 358    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 359    public IEnumerable<ILocomotion> GetLocomotions()
 360    {
 8361        yield return Move;
 8362        yield return Platform;
 363
 8364        if (Jump != null)
 8365            yield return Jump;
 366
 8367        yield return Fall;
 368
 8369        if (Water != null)
 8370            yield return Water;
 371
 8372        if (Fly != null)
 8373            yield return Fly;
 374
 8375        if (Climb != null)
 8376            yield return Climb;
 377
 8378        if (Slide != null)
 8379            yield return Slide;
 8380    }
 381
 382    private void SetLocomotion(Type type, ILocomotion? locomotion)
 383    {
 15384        if (!TryResolveLocomotionSlot(type, out LocomotionSlot slot))
 1385            throw new NotSupportedException($"Unsupported locomotion type '{type.Name}'.");
 386
 14387        SetLocomotion(slot, locomotion);
 14388    }
 389
 390    private static bool TryResolveLocomotionSlot(Type type, out LocomotionSlot slot)
 391    {
 72392        if (type == typeof(MoveLocomotion))
 393        {
 6394            slot = LocomotionSlot.Move;
 6395            return true;
 396        }
 397
 66398        if (type == typeof(PlatformLocomotion))
 399        {
 2400            slot = LocomotionSlot.Platform;
 2401            return true;
 402        }
 403
 64404        if (type == typeof(JumpLocomotion))
 405        {
 8406            slot = LocomotionSlot.Jump;
 8407            return true;
 408        }
 409
 56410        if (type == typeof(FallLocomotion))
 411        {
 20412            slot = LocomotionSlot.Fall;
 20413            return true;
 414        }
 415
 36416        if (type == typeof(SlideLocomotion))
 417        {
 4418            slot = LocomotionSlot.Slide;
 4419            return true;
 420        }
 421
 32422        if (type == typeof(WaterLocomotion))
 423        {
 11424            slot = LocomotionSlot.Water;
 11425            return true;
 426        }
 427
 21428        if (type == typeof(FlyLocomotion))
 429        {
 2430            slot = LocomotionSlot.Fly;
 2431            return true;
 432        }
 433
 19434        if (type == typeof(ClimbLocomotion))
 435        {
 15436            slot = LocomotionSlot.Climb;
 15437            return true;
 438        }
 439
 4440        slot = default;
 4441        return false;
 442    }
 443
 444    private void SetLocomotion(LocomotionSlot slot, ILocomotion? locomotion)
 445    {
 14446        BindLocomotion(locomotion);
 447
 448        switch (slot)
 449        {
 450            case LocomotionSlot.Move:
 1451                _move = (MoveLocomotion)locomotion!;
 1452                return;
 453            case LocomotionSlot.Platform:
 1454                _platform = (PlatformLocomotion)locomotion!;
 1455                return;
 456            case LocomotionSlot.Jump:
 3457                _jump = locomotion as JumpLocomotion;
 3458                return;
 459            case LocomotionSlot.Fall:
 1460                _fall = (FallLocomotion)locomotion!;
 1461                return;
 462            case LocomotionSlot.Slide:
 2463                _slide = locomotion as SlideLocomotion;
 2464                return;
 465            case LocomotionSlot.Water:
 3466                _water = locomotion as WaterLocomotion;
 3467                return;
 468            case LocomotionSlot.Fly:
 1469                _fly = locomotion as FlyLocomotion;
 1470                return;
 471            case LocomotionSlot.Climb:
 2472                _climb = locomotion as ClimbLocomotion;
 2473                return;
 474        }
 475    }
 476
 477    private void BindInstalledLocomotions()
 478    {
 1644479        BindLocomotion(_move);
 1644480        BindLocomotion(_platform);
 1644481        BindLocomotion(_jump);
 1644482        BindLocomotion(_fall);
 1644483        BindLocomotion(_slide);
 1644484        BindLocomotion(_water);
 1644485        BindLocomotion(_fly);
 1644486        BindLocomotion(_climb);
 1644487    }
 488
 489    private void BindLocomotion(ILocomotion? locomotion)
 490    {
 13166491        if (_context == null || locomotion == null)
 6164492            return;
 493
 494        switch (locomotion)
 495        {
 496            case JumpLocomotion jump:
 866497                jump.BindContext(_context);
 866498                break;
 499            case PlatformLocomotion platform:
 893500                platform.BindContext(_context);
 893501                break;
 502            case WaterLocomotion water:
 864503                water.BindContext(_context);
 504                break;
 505        }
 864506    }
 507
 508    private static void ClearReplacedLocomotion(ILocomotion? current, ILocomotion? next)
 509    {
 6920510        if (current == null || ReferenceEquals(current, next))
 6445511            return;
 512
 475513        current.ClearTransientState();
 475514    }
 515
 516    #endregion
 517
 518    #region Transient State Management
 519
 520    /// <summary>
 521    /// Synchronizes locomotion states with another <see cref="LocomotionHandler"/> instance.
 522    /// </summary>
 523    /// <remarks>
 524    /// This ensures that all locomotion modules maintain consistent movement behavior when synchronizing states,
 525    /// which is useful for rollback systems or deterministic simulations.
 526    /// </remarks>
 527    /// <param name="other">The locomotion handler instance to sync with.</param>
 528    public void SyncTransientState(LocomotionHandler other)
 529    {
 3530        if (other == null) return;
 531
 1532        IsInControl = other.IsInControl;
 533
 18534        foreach (var locomotion in GetLocomotions())
 535        {
 8536            if (!locomotion.IsEnabled) continue;
 7537            ILocomotion? otherLocomotion = other.GetLocomotion(locomotion.GetType());
 7538            if (otherLocomotion == null) continue;
 3539            locomotion.SyncTransientState(otherLocomotion);
 540        }
 1541    }
 542
 543    /// <summary>
 544    /// Clears the transient state for the locomotion component of the specified type, if it is enabled.
 545    /// </summary>
 546    /// <remarks>This method has no effect if the specified locomotion component is not present or is not enabled.</rema
 547    /// <typeparam name="T">The type of locomotion component for which to clear transient state. Must implement the ILoc
 548    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 549    public void ClearTransientState<T>() where T : ILocomotion
 550    {
 37551        var locomotion = GetLocomotion(typeof(T));
 37552        if (locomotion != null && locomotion.IsEnabled)
 37553            locomotion.ClearTransientState();
 37554    }
 555
 556    /// <summary>
 557    /// Clears the transient state of all locomotion modules.
 558    /// </summary>
 559    /// <remarks>
 560    /// This method resets movement states without altering locomotion configurations,
 561    /// ensuring a clean reset of position, velocity, and state-based properties.
 562    /// </remarks>
 563    public void ClearAllTransientState()
 564    {
 108565        foreach (var locomotion in GetLocomotions())
 566        {
 48567            if (locomotion.IsEnabled)
 48568                locomotion.ClearTransientState();
 569        }
 6570    }
 571
 572    #endregion
 573
 574    #region Serialization
 575
 576    /// <inheritdoc />
 577    public void RecordData(IChronicler chronicler)
 578    {
 106579        RecordValues.Look(chronicler, ref IsInControl, "IsInControl", true);
 106580        int installedKinds = (int)InstalledKinds;
 106581        RecordValues.Look(chronicler, ref installedKinds, "InstalledKinds", (int)LocomotionKind.All);
 582
 106583        if (chronicler.Mode == SerializationMode.Loading)
 53584            ConfigureInstalledKinds((LocomotionKind)installedKinds);
 585
 106586        RecordDeep.Look(chronicler, ref _forces, "Forces");
 106587        RecordDeep.Look(chronicler, ref _move, "Move");
 106588        RecordDeep.Look(chronicler, ref _platform, "Platform");
 106589        RecordOptionalLocomotion(chronicler, ref _jump, "Jump");
 106590        RecordDeep.Look(chronicler, ref _fall, "Fall");
 106591        RecordOptionalLocomotion(chronicler, ref _slide, "Slide");
 106592        RecordOptionalLocomotion(chronicler, ref _water, "Water");
 106593        RecordOptionalLocomotion(chronicler, ref _fly, "Fly");
 106594        RecordOptionalLocomotion(chronicler, ref _climb, "Climb");
 595
 106596        if (chronicler.Mode == SerializationMode.Loading)
 597        {
 53598            ApplyProfile(new LocomotionProfile(
 53599                _move,
 53600                _fall,
 53601                _platform,
 53602                _jump,
 53603                _slide,
 53604                _water,
 53605                _fly,
 53606                _climb));
 607        }
 106608    }
 609
 610    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 611    private static void RecordOptionalLocomotion<T>(
 612        IChronicler chronicler,
 613        ref T? locomotion,
 614        string id)
 615        where T : class, ILocomotion, IRecordable
 616    {
 530617        T local = locomotion ?? null!;
 530618        RecordDeep.Look(chronicler, ref local, id);
 530619        locomotion = local;
 530620    }
 621
 622    #endregion
 623}