using System.Collections;
using UnityEngine;
using UnityEngine.Events;
/// <summary>
/// GAME 1303 Tier 4 — Spikes: always out, popping up on a timer, or springing when you
/// step near. Cutthroat Course, Roach Invasion, Stardust, Hot Air (mountain peaks).
///
/// SETUP
/// 1. Parent "SpikeTrap" (empty, or a floor-plate sprite) with this script.
/// 2. Child "Spikes": the spike sprite + BoxCollider2D (Is Trigger) + DamageOnContact.
/// Drag the child into Spikes. Set Hidden Offset (e.g. y = -0.6) — where the spikes
/// sit when retracted, relative to the parent. Shown = (0,0).
/// 3. Mode:
/// Static – always out. (You could skip this script entirely — it's just
/// DamageOnContact — but keeping it lets you toggle later.)
/// Timed – out for Out Time, in for In Time, forever. Warning Time shakes/plays
/// a click before popping so it's fair.
/// Triggered – a separate trigger child ("TriggerArea", drag into Trigger Zone
/// slot) pops the spikes when the Player enters, after Warning Time.
/// 4. Damage only applies while extended (the child's DamageOnContact is toggled).
///
/// Start Extended / Start Delay let you offset traps in a row so they pop in sequence.
/// </summary>
public class SpikeTrap : MonoBehaviour
{
public enum Mode { Static, Timed, Triggered }
public Mode mode = Mode.Timed;
public Transform spikes;
public Vector2 hiddenOffset = new Vector2(0f, -0.6f);
public float moveSpeed = 12f;
[Header("Timed")]
public float outTime = 1.5f;
public float inTime = 1.5f;
public float startDelay = 0f;
public bool startExtended = false;
[Header("Triggered")]
public Collider2D triggerZone;
public float retractAfter = 1f;
public bool rearm = true;
[Header("Warning")]
public float warningTime = 0.3f;
public float shakeAmount = 0.05f;
public string warningSfx = "spike_warn";
public string popSfx = "spike_pop";
public UnityEvent OnExtended;
public UnityEvent OnRetracted;
public bool IsExtended { get; private set; }
private Vector3 shownLocal;
private Vector3 hiddenLocal;
private DamageOnContact damage;
private bool busy;
private readonly Collider2D[] overlapResults = new Collider2D[8];
private ContactFilter2D noFilter;
void Awake()
{
if (spikes == null) { Debug.LogWarning("SpikeTrap: assign the Spikes child."); enabled = false; return; }
shownLocal = Vector3.zero;
hiddenLocal = (Vector3)hiddenOffset;
damage = spikes.GetComponent<DamageOnContact>();
noFilter = new ContactFilter2D();
noFilter.NoFilter();
}
void Start()
{
switch (mode)
{
case Mode.Static:
SetInstant(true);
break;
case Mode.Timed:
SetInstant(startExtended);
StartCoroutine(TimedLoop());
break;
case Mode.Triggered:
SetInstant(false);
if (triggerZone != null) triggerZone.isTrigger = true;
break;
}
}
IEnumerator TimedLoop()
{
yield return new WaitForSeconds(startDelay);
while (true)
{
if (IsExtended)
{
yield return new WaitForSeconds(outTime);
yield return Move(false);
}
else
{
yield return new WaitForSeconds(Mathf.Max(0f, inTime - warningTime));
yield return Warn();
yield return Move(true);
}
}
}
// Triggered mode: relay from the trigger zone. The zone needs a tiny forwarder —
// we handle it by checking overlaps ourselves in Update to avoid extra scripts.
void Update()
{
if (mode != Mode.Triggered || triggerZone == null || busy || IsExtended) return;
int n = triggerZone.Overlap(noFilter, overlapResults); // Unity 2022+/6 API (older: OverlapCollider)
for (int i = 0; i < n; i++)
if (overlapResults[i] != null && overlapResults[i].CompareTag("Player")) { StartCoroutine(TriggeredPop()); break; }
}
IEnumerator TriggeredPop()
{
busy = true;
yield return Warn();
yield return Move(true);
yield return new WaitForSeconds(retractAfter);
yield return Move(false);
if (!rearm) enabled = false;
busy = false;
}
IEnumerator Warn()
{
if (!string.IsNullOrEmpty(warningSfx)) AudioManager.Instance?.PlaySFX(warningSfx);
float t = 0f;
Vector3 basePos = spikes.localPosition;
while (t < warningTime)
{
t += Time.deltaTime;
spikes.localPosition = basePos + (Vector3)Random.insideUnitCircle * shakeAmount;
yield return null;
}
spikes.localPosition = basePos;
}
IEnumerator Move(bool extend)
{
Vector3 target = extend ? shownLocal : hiddenLocal;
if (extend)
{
if (damage) damage.enabled = true;
if (!string.IsNullOrEmpty(popSfx)) AudioManager.Instance?.PlaySFX(popSfx);
}
while (Vector3.Distance(spikes.localPosition, target) > 0.01f)
{
spikes.localPosition = Vector3.MoveTowards(spikes.localPosition, target, moveSpeed * Time.deltaTime);
yield return null;
}
spikes.localPosition = target;
IsExtended = extend;
if (!extend && damage) damage.enabled = false;
if (extend) OnExtended?.Invoke(); else OnRetracted?.Invoke();
}
void SetInstant(bool extended)
{
spikes.localPosition = extended ? shownLocal : hiddenLocal;
IsExtended = extended;
if (damage) damage.enabled = extended;
}
}