ManeuverAbility.cs

Component that gives an entity dash and parry abilities. It handles input for dash and parry, cooldowns and timing windows, plays sounds and spawns particle GameObjects, and exposes bot-callable methods to trigger dash/parry.

File Access
using Sandbox;
using System;

public sealed class ManeuverAbility : Component
{
    [Property] public float DashForce { get; set; } = 900f;
    [Property] public float DashCooldown { get; set; } = 1.2f;

    [Property] public float ParryWindow { get; set; } = 0.4f;
    [Property] public float ParryCooldown { get; set; } = 2.5f;

    [Property] public SoundEvent DashSound { get; set; }
    [Property] public SoundEvent ParrySound { get; set; }
    [Property] public SoundEvent ReflectSound { get; set; }
    [Property] public GameObject DashParticle { get; set; }
    [Property] public GameObject ParryParticle { get; set; }

    private TimeSince _lastDash;
    private TimeSince _lastParry;
    private TimeSince _parryTimer;

    public bool IsParrying => _parryTimer < ParryWindow;
    public bool IsDashReady => _lastDash >= DashCooldown;
    public bool IsParryReady => _lastParry >= ParryCooldown;

    protected override void OnUpdate()
    {
        if ( IsProxy ) return;

        if ( Input.Pressed( "run" ) && IsDashReady )
        {
            ExecuteDash();
        }

        if ( Input.Pressed( "attack2" ) && IsParryReady )
        {
            ExecuteParry();
        }
    }

    private void ExecuteDash()
    {
        var cc = Components.Get<CharacterController>();
        if ( !cc.IsValid() ) return;

        var moveInput = Input.AnalogMove;
        var wishDir = (WorldTransform.Forward * moveInput.x + WorldTransform.Left * moveInput.y).Normal;

        if ( wishDir.IsNearlyZero() )
        {
            wishDir = WorldTransform.Forward;
        }

        PerformDash( wishDir );
    }

    private void ExecuteParry()
    {
        PerformParry();
    }

    // --- МЕТОДЫ ДЛЯ БОТОВ И ВНЕШНИХ ВЫЗОВОВ ---

    public void TryExecuteBotDash( Vector3 direction )
    {
        if ( !IsDashReady ) return;
        PerformDash( direction );
    }

    public void TryExecuteBotParry()
    {
        if ( !IsParryReady ) return;
        PerformParry();
    }

    private void PerformDash( Vector3 direction )
    {
        _lastDash = 0;

        var cc = Components.Get<CharacterController>();
        if ( cc.IsValid() )
        {
            cc.Velocity += direction.Normal * DashForce;

            if ( DashSound != null ) Sound.Play( DashSound, WorldPosition );
            if ( DashParticle.IsValid() ) DashParticle.Clone( WorldPosition );
        }
    }

    private void PerformParry()
    {
        _lastParry = 0;
        _parryTimer = 0;

        if ( ParrySound != null ) Sound.Play( ParrySound, WorldPosition );
        if ( ParryParticle.IsValid() ) ParryParticle.Clone( WorldPosition );
    }

    public void OnSuccessfulReflect( Vector3 reflectOrigin )
    {
        if ( ReflectSound != null )
        {
            Sound.Play( ReflectSound, WorldPosition );
        }
    }
}