using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.InputSystem;
/// <summary>
/// GAME 1303 Tier 3 — Magnet gloves. Hold to pull metal objects toward you; let go to
/// drop them, or press Attack while holding one to throw it. The Magnet's core mechanic.
///
/// SETUP
/// 1. Player: this script + an empty child "HoldPoint" in front of the chest (drag it in).
/// 2. Input: add a "Magnet" Button action to the Player map (Right Mouse / gamepad
/// Left Trigger). Listens for OnMagnet. Throw uses the default "Attack" action.
/// 3. Metal objects: any object with a Rigidbody2D (Dynamic) on a layer called "Metal".
/// Set Metal Layer here. Crates, enemy shields, enemy weapons, the boss's armor plate.
/// 4. A thrown object should hurt enemies: add a Hitbox (Target Enemy, disabled) as a
/// child of the metal object and drag it into MetalObject.throwHitbox, OR simpler —
/// give the metal object a DamageOnContact (Target Tag "Enemy") that this script
/// enables only while it's flying. Use the MetalObject helper script for that.
/// 5. Pull Force scales with distance so far objects come slowly and near ones snap in.
/// Max Objects limits how many you drag at once (1 = single-object puzzle feel).
///
/// Ideas from the pitch: pull a masked criminal's metal gun out of his hands (put the
/// gun on the Metal layer as a separate rigidbody), yank Blaster's shield plate off to
/// expose a Hurtbox weak point.
/// </summary>
public class MagnetPull : MonoBehaviour
{
public LayerMask metalLayer;
public float radius = 6f;
[Tooltip("Only pull objects roughly in front of you (degrees each side of facing). 180 = all around.")]
[Range(10f, 180f)] public float coneAngle = 60f;
public float pullForce = 30f;
public int maxObjects = 3;
[Header("Hold & throw")]
public Transform holdPoint;
[Tooltip("Distance at which a pulled object snaps into the hold point.")]
public float grabDistance = 0.8f;
public float throwSpeed = 18f;
public bool canHold = true;
[Header("Feel")]
public string pullSfx = "magnet";
public string throwSfx = "throw";
public LineRenderer beamPrefab; // optional: one line per pulled object
[Header("Events")]
public UnityEvent OnMagnetOn;
public UnityEvent OnMagnetOff;
public UnityEvent<Rigidbody2D> OnGrabbed;
public UnityEvent<Rigidbody2D> OnThrown;
public bool IsActive { get; private set; }
public Rigidbody2D HeldObject { get; private set; }
private PlayerController2D controller;
private readonly List<Rigidbody2D> pulling = new List<Rigidbody2D>();
private readonly List<LineRenderer> beams = new List<LineRenderer>();
private Vector2 Facing => controller != null ? controller.Facing : (transform.lossyScale.x >= 0 ? Vector2.right : Vector2.left);
void Awake()
{
controller = GetComponent<PlayerController2D>();
if (holdPoint == null) holdPoint = transform;
}
void OnMagnet(InputValue v)
{
if (v.isPressed) { if (!IsActive) { IsActive = true; OnMagnetOn?.Invoke(); AudioManager.Instance?.PlaySFX(pullSfx); } }
else Deactivate();
}
void OnAttack(InputValue v)
{
if (v.isPressed && HeldObject != null) Throw();
}
void Deactivate()
{
if (!IsActive) return;
IsActive = false;
pulling.Clear();
ClearBeams();
// Drop held object gently
if (HeldObject != null) ReleaseHeld(Vector2.zero);
OnMagnetOff?.Invoke();
}
void FixedUpdate()
{
if (!IsActive) { return; }
// Refresh the set of objects in range and in the cone
pulling.Clear();
var hits = Physics2D.OverlapCircleAll(transform.position, radius, metalLayer);
foreach (var h in hits)
{
var rb = h.attachedRigidbody;
if (rb == null || rb.bodyType != RigidbodyType2D.Dynamic || rb == HeldObject) continue;
Vector2 to = (Vector2)rb.position - (Vector2)transform.position;
if (Vector2.Angle(Facing, to) > coneAngle) continue;
if (!pulling.Contains(rb)) pulling.Add(rb);
if (pulling.Count >= maxObjects) break;
}
// Pull
foreach (var rb in pulling)
{
Vector2 to = (Vector2)holdPoint.position - rb.position;
float dist = to.magnitude;
if (canHold && HeldObject == null && dist <= grabDistance) { Grab(rb); continue; }
// Stronger when closer (inverse-ish), damped so it doesn't orbit
float strength = pullForce * Mathf.Clamp01(1.5f - dist / radius);
rb.AddForce(to.normalized * strength);
rb.linearVelocity *= 0.95f;
}
// Keep held object at hold point
if (HeldObject != null)
{
HeldObject.position = holdPoint.position;
HeldObject.linearVelocity = Vector2.zero;
HeldObject.angularVelocity = 0f;
}
UpdateBeams();
}
void Grab(Rigidbody2D rb)
{
HeldObject = rb;
rb.bodyType = RigidbodyType2D.Kinematic;
rb.linearVelocity = Vector2.zero;
var mo = rb.GetComponent<MetalObject>();
if (mo != null) mo.OnGrabbed(this);
OnGrabbed?.Invoke(rb);
}
void Throw()
{
var rb = HeldObject;
ReleaseHeld(Facing * throwSpeed);
AudioManager.Instance?.PlaySFX(throwSfx);
var mo = rb.GetComponent<MetalObject>();
if (mo != null) mo.OnThrown();
OnThrown?.Invoke(rb);
}
void ReleaseHeld(Vector2 velocity)
{
if (HeldObject == null) return;
HeldObject.bodyType = RigidbodyType2D.Dynamic;
HeldObject.linearVelocity = velocity;
var mo = HeldObject.GetComponent<MetalObject>();
if (mo != null && velocity == Vector2.zero) mo.OnDropped();
HeldObject = null;
}
// ----- Beam visuals -----
void UpdateBeams()
{
if (beamPrefab == null) return;
while (beams.Count < pulling.Count) { var b = Instantiate(beamPrefab, transform); b.positionCount = 2; beams.Add(b); }
for (int i = 0; i < beams.Count; i++)
{
bool on = i < pulling.Count;
beams[i].enabled = on;
if (on) { beams[i].SetPosition(0, holdPoint.position); beams[i].SetPosition(1, pulling[i].position); }
}
}
void ClearBeams() { foreach (var b in beams) if (b) b.enabled = false; }
void OnDrawGizmosSelected()
{
Gizmos.color = new Color(1f, 0.5f, 0f, 0.5f);
Gizmos.DrawWireSphere(transform.position, radius);
Vector3 f = Application.isPlaying ? (Vector3)Facing : Vector3.right;
Gizmos.DrawLine(transform.position, transform.position + Quaternion.Euler(0, 0, coneAngle) * f * radius);
Gizmos.DrawLine(transform.position, transform.position + Quaternion.Euler(0, 0, -coneAngle) * f * radius);
}
}