using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.InputSystem;
/// <summary>
/// GAME 1303 Tier 3 — RUN REALLY FAST AND THROW YOURSELF AT PEOPLE. Missile Man's core.
///
/// How it plays: while running, a charge meter fills. Press Attack with enough charge
/// and Missile Man launches himself in his facing direction, invulnerable, with a
/// hitbox on. Damage scales with charge. Hitting a wall ends the launch (and can hurt
/// him a little, if you like).
///
/// SETUP
/// 1. Player: PlayerController2D v1.1 + Health + Knockback + this script + a DISABLED
/// child "LaunchHitbox" with Hitbox (Target Enemy, big knockback). Drag it in.
/// 2. Listens to "Attack" (default action). If you also use MeleeAttack, set that to
/// Player Controlled OFF — Missile Man doesn't punch anyway.
/// 3. Charge Rate: seconds of running at near-full speed to reach full charge.
/// Min Charge To Launch: 0.3 means you can launch weakly after a short run.
/// 4. OnChargeChanged(0-1) -> a HUD bar. OnLaunched / OnLaunchEnded for SFX, trails.
/// 5. Optional Wall Layer: the launch stops when the front raycast hits it.
///
/// Zap Attack can use this too as a "charged dash-through".
/// </summary>
public class MomentumLauncher : MonoBehaviour
{
[Header("Charge")]
public float chargeRate = 1.5f;
[Tooltip("Fraction of moveSpeed you must be moving at for charge to build.")]
[Range(0f, 1f)] public float speedFractionToCharge = 0.8f;
public float chargeDecayRate = 1f; // per second when not running
[Range(0f, 1f)] public float minChargeToLaunch = 0.3f;
[Header("Launch")]
public float launchSpeedMin = 15f;
public float launchSpeedMax = 30f;
public float launchDuration = 0.5f;
public bool invulnerableWhileLaunching = true;
[Tooltip("Hitbox damage at full charge (min charge does 1).")]
public int maxDamage = 5;
public Hitbox launchHitbox;
public LayerMask wallLayer;
public float wallCheckDistance = 0.5f;
public int wallImpactSelfDamage = 0;
[Header("Feel")]
public string animatorTrigger = "Launch";
public string sfxName = "launch";
[Header("Events")]
public UnityEvent<float> OnChargeChanged;
public UnityEvent OnLaunched;
public UnityEvent OnLaunchEnded;
public float Charge { get; private set; }
public bool IsLaunching { get; private set; }
private PlayerController2D controller;
private Rigidbody2D rb;
private Health health;
private Animator anim;
private Collider2D col;
void Awake()
{
controller = GetComponent<PlayerController2D>();
rb = GetComponent<Rigidbody2D>();
health = GetComponent<Health>();
anim = GetComponent<Animator>();
col = GetComponent<Collider2D>();
if (launchHitbox != null) launchHitbox.gameObject.SetActive(false);
}
void Update()
{
if (IsLaunching) return;
float threshold = controller.moveSpeed * controller.SpeedMultiplier * speedFractionToCharge;
bool running = controller.IsGrounded && Mathf.Abs(controller.Velocity.x) >= threshold;
float before = Charge;
Charge = running
? Mathf.Min(1f, Charge + Time.deltaTime / chargeRate)
: Mathf.Max(0f, Charge - Time.deltaTime * chargeDecayRate);
if (!Mathf.Approximately(before, Charge)) OnChargeChanged?.Invoke(Charge);
}
void OnAttack(InputValue v)
{
if (!v.isPressed) return;
TryLaunch();
}
public bool TryLaunch()
{
if (IsLaunching || Charge < minChargeToLaunch || !controller.InputEnabled || controller.MovementLocked) return false;
StartCoroutine(LaunchRoutine());
return true;
}
IEnumerator LaunchRoutine()
{
IsLaunching = true;
float power = Charge; // 0.3 .. 1
Charge = 0f;
OnChargeChanged?.Invoke(0f);
float speed = Mathf.Lerp(launchSpeedMin, launchSpeedMax, power);
Vector2 dir = controller.Facing;
controller.MovementLocked = true;
rb.gravityScale = 0f;
if (invulnerableWhileLaunching && health != null) health.SetInvulnerable(launchDuration);
if (launchHitbox != null)
{
launchHitbox.damage = Mathf.Max(1, Mathf.RoundToInt(maxDamage * power));
launchHitbox.gameObject.SetActive(true);
}
if (anim != null && !string.IsNullOrEmpty(animatorTrigger)) anim.SetTrigger(animatorTrigger);
if (!string.IsNullOrEmpty(sfxName)) AudioManager.Instance?.PlaySFX(sfxName);
OnLaunched?.Invoke();
float t = 0f;
while (t < launchDuration)
{
rb.linearVelocity = dir * speed;
t += Time.fixedDeltaTime;
// Wall in the way?
float half = col != null ? col.bounds.extents.x : 0.5f;
if (Physics2D.Raycast(rb.position, dir, half + wallCheckDistance, wallLayer))
{
if (wallImpactSelfDamage > 0 && health != null)
{
health.ClearInvulnerability();
health.TakeDamage(wallImpactSelfDamage);
}
break;
}
yield return new WaitForFixedUpdate();
}
if (launchHitbox != null) launchHitbox.gameObject.SetActive(false);
rb.linearVelocity = new Vector2(rb.linearVelocity.x * 0.3f, rb.linearVelocity.y);
rb.gravityScale = controller.BaseGravity;
controller.MovementLocked = false;
IsLaunching = false;
OnLaunchEnded?.Invoke();
}
}