using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.InputSystem;
/// <summary>
/// GAME 1303 Tier 3 — Burst of speed in a direction. Zap Attack, Stardust, Apep's Trial,
/// Spark of Corruption.
///
/// SETUP
/// 1. Add to the player (needs PlayerController2D v1.1 and PlayerInput = Send Messages).
/// 2. Add a "Dash" action to your Input Actions asset (Player map): Button type,
/// bind Left Shift and gamepad East/B. This script listens for OnDash.
/// (Or tick Use Sprint Action to reuse the built-in "Sprint" binding.)
/// 3. Direction: Facing = horizontal only (classic). EightWay = uses the stick (Celeste).
/// 4. Air Dashes: how many before landing resets it. 0 = ground only.
/// 5. Invulnerable During Dash: sets Health invulnerability for the dash duration.
/// 6. Optional: an "Dash" Animator trigger, a "dash" sound, a ghost-trail prefab.
///
/// Zap Attack's "dash through enemies": tick Invulnerable and add a child Hitbox that
/// OnDashStarted enables and OnDashEnded disables.
/// </summary>
public class Dash : MonoBehaviour
{
public enum DirectionMode { Facing, EightWay }
public float dashSpeed = 22f;
public float dashDuration = 0.15f;
public float cooldown = 0.5f;
public DirectionMode direction = DirectionMode.Facing;
[Tooltip("Dashes allowed in the air before touching ground.")]
public int airDashes = 1;
public bool invulnerableDuringDash = true;
[Tooltip("Keep some upward/downward momentum after a horizontal dash? 0 = stop dead.")]
[Range(0f, 1f)] public float keepVelocityAfter = 0f;
[Header("Input")]
public bool useSprintAction = false;
[Header("Feel")]
public string animatorTrigger = "Dash";
public string sfxName = "dash";
[Header("Events")]
public UnityEvent OnDashStarted;
public UnityEvent OnDashEnded;
public bool IsDashing { get; private set; }
private PlayerController2D controller;
private Rigidbody2D rb;
private Animator anim;
private Health health;
private float cooldownTimer;
private int airDashesLeft;
void Awake()
{
controller = GetComponent<PlayerController2D>();
rb = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
health = GetComponent<Health>();
airDashesLeft = airDashes;
}
void OnEnable() { controller.OnLanded.AddListener(ResetAirDashes); }
void OnDisable() { controller.OnLanded.RemoveListener(ResetAirDashes); }
void Update() { cooldownTimer -= Time.deltaTime; }
void OnDash(InputValue v) { if (!useSprintAction && v.isPressed) TryDash(); }
void OnSprint(InputValue v) { if (useSprintAction && v.isPressed) TryDash(); }
public bool TryDash()
{
if (IsDashing || cooldownTimer > 0f || !controller.InputEnabled || controller.MovementLocked) return false;
if (!controller.IsGrounded)
{
if (airDashesLeft <= 0) return false;
airDashesLeft--;
}
Vector2 dir = controller.Facing;
if (direction == DirectionMode.EightWay && controller.MoveVector.sqrMagnitude > 0.1f)
{
Vector2 mv = controller.MoveVector;
dir = new Vector2(Mathf.Round(mv.x), Mathf.Round(mv.y)).normalized;
if (dir.x != 0) controller.FaceDirection(dir.x);
}
StartCoroutine(DashRoutine(dir));
return true;
}
IEnumerator DashRoutine(Vector2 dir)
{
IsDashing = true;
cooldownTimer = cooldown;
controller.MovementLocked = true;
rb.gravityScale = 0f;
if (anim != null && !string.IsNullOrEmpty(animatorTrigger)) anim.SetTrigger(animatorTrigger);
if (!string.IsNullOrEmpty(sfxName)) AudioManager.Instance?.PlaySFX(sfxName);
if (invulnerableDuringDash && health != null) health.SetInvulnerable(dashDuration);
OnDashStarted?.Invoke();
float t = 0f;
while (t < dashDuration)
{
rb.linearVelocity = dir * dashSpeed;
t += Time.fixedDeltaTime;
yield return new WaitForFixedUpdate();
}
rb.linearVelocity = rb.linearVelocity * keepVelocityAfter;
rb.gravityScale = controller.BaseGravity;
controller.MovementLocked = false;
IsDashing = false;
OnDashEnded?.Invoke();
}
void ResetAirDashes() => airDashesLeft = airDashes;
}