using UnityEngine;
using UnityEngine.Events;
using UnityEngine.InputSystem;
/// <summary>
/// GAME 1303 Tier 2 — Spawns Projectiles from a fire point.
///
/// SETUP
/// 1. Add to the shooter's root. Create an empty child "FirePoint" at the muzzle /
/// hand and drag it in. Assign a Projectile prefab.
/// 2. Team: Player for the player, Enemy for enemies (so bullets don't hit the owner).
/// 3. Mode:
/// PlayerInput – fires on the Input System "Attack" action (Send Messages).
/// If the player ALSO has MeleeAttack on "Attack", set one of them
/// to Manual and call it from a different action.
/// AutoFire – fires every Fire Rate seconds (turrets, bosses, sorcerer NPC).
/// Manual – only when something calls Fire() (BossController, EnemyChase).
/// 4. Direction: Facing uses the owner's X scale (flips with PlayerController2D);
/// TowardPlayer aims at the Player tag (enemy shooters); FirePointRight uses
/// the fire point's own right vector (rotate the FirePoint to aim).
/// </summary>
public class ProjectileShooter : MonoBehaviour
{
public enum Mode { PlayerInput, AutoFire, Manual }
public enum AimMode { Facing, TowardPlayer, FirePointRight }
public Projectile projectilePrefab;
public Transform firePoint;
public Team team = Team.Player;
public Mode mode = Mode.PlayerInput;
public AimMode aim = AimMode.Facing;
[Tooltip("Minimum seconds between shots.")]
public float fireRate = 0.3f;
[Tooltip("Shots per press (3 = burst). Spread is degrees between them.")]
public int shotsPerFire = 1;
public float spreadDegrees = 0f;
[Header("Feel")]
public string animatorTrigger = "Shoot";
public string sfxName = "shoot";
public UnityEvent OnFired;
private float cooldown;
private Animator anim;
private ComboCounter combo;
private Transform player;
void Awake()
{
anim = GetComponent<Animator>();
combo = GetComponent<ComboCounter>();
if (firePoint == null) firePoint = transform;
}
void Update()
{
cooldown -= Time.deltaTime;
if (mode == Mode.AutoFire && cooldown <= 0f) Fire();
}
void OnAttack(InputValue value)
{
if (mode != Mode.PlayerInput || !value.isPressed) return;
Fire();
}
/// <summary>Fire if off cooldown. Returns true if a shot was fired.</summary>
public bool Fire()
{
if (cooldown > 0f || projectilePrefab == null) return false;
cooldown = fireRate;
Vector2 dir = GetAimDirection();
float startAngle = -spreadDegrees * (shotsPerFire - 1) / 2f;
for (int i = 0; i < shotsPerFire; i++)
{
float angle = startAngle + spreadDegrees * i;
Vector2 d = Quaternion.Euler(0, 0, angle) * dir;
Projectile p = Instantiate(projectilePrefab, firePoint.position, Quaternion.identity);
p.Launch(d, team, team == Team.Player ? combo : null);
}
if (anim != null && !string.IsNullOrEmpty(animatorTrigger)) anim.SetTrigger(animatorTrigger);
if (!string.IsNullOrEmpty(sfxName)) AudioManager.Instance?.PlaySFX(sfxName);
OnFired?.Invoke();
return true;
}
Vector2 GetAimDirection()
{
switch (aim)
{
case AimMode.TowardPlayer:
if (player == null)
{
var p = GameObject.FindGameObjectWithTag("Player");
if (p != null) player = p.transform;
}
if (player != null) return (player.position - firePoint.position).normalized;
goto case AimMode.Facing;
case AimMode.FirePointRight:
return firePoint.right;
default: // Facing — sign of X scale
return transform.lossyScale.x >= 0f ? Vector2.right : Vector2.left;
}
}
}