using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
/// <summary>
/// GAME 1303 Tier 8 — Several gauges drifting toward failure at once; the player runs
/// between them keeping everything in the safe band. Cephalopods in Space! (the instructor
/// demo: "attend to all the controls and watch the display").
///
/// This is the whole game in one script, which makes it a good in-class build: it's a
/// complete loop students can read in five minutes, and every field changes how it feels.
///
/// SETUP
/// 1. Empty GameObject "ShipSystems" + this script.
/// 2. Add a Gauge per control (Oxygen, Heat, Ink Pressure, Course). Each has a drift rate,
/// a safe band, and how much one adjustment moves it.
/// 3. For each gauge, make an Interactable (Tier 6) somewhere in the cockpit and wire its
/// OnInteract -> MultiTaskStation.Adjust with the gauge's index (0, 1, 2...).
/// Eight arms, eight controls — that's the joke and the mechanic.
/// 4. OnGaugeChanged(index, value) drives a UI Slider per gauge; colour it red outside
/// the safe band.
/// 5. Failure: each gauge outside its band accumulates Danger. When Danger hits 1, the
/// run ends. Difficulty Ramp makes everything drift faster over time, so a run always
/// ends eventually — the score is how long you lasted.
///
/// Tuning tip: start with drift rates so slow it's boring, then speed up until you're
/// slightly panicked. That's the right value.
/// </summary>
public class MultiTaskStation : MonoBehaviour
{
[System.Serializable]
public class Gauge
{
public string label = "Oxygen";
[Range(0f, 1f)] public float value = 0.5f;
[Tooltip("Which way it drifts per second when ignored (negative = falls).")]
public float driftPerSecond = -0.06f;
[Range(0f, 1f)] public float safeMin = 0.35f;
[Range(0f, 1f)] public float safeMax = 0.75f;
[Tooltip("How much one interaction moves it.")]
public float adjustAmount = 0.25f;
[Tooltip("Adjusting pushes it the opposite way to the drift.")]
public bool adjustOpposesDrift = true;
public Sprite icon;
public bool InSafeBand => value >= safeMin && value <= safeMax;
}
[Header("Controls")]
public List<Gauge> gauges = new List<Gauge>();
[Header("Failure")]
[Tooltip("Danger gained per second per gauge outside its band.")]
public float dangerPerSecond = 0.12f;
[Tooltip("Danger recovered per second when everything is safe.")]
public float dangerRecovery = 0.08f;
[Header("Difficulty")]
[Tooltip("Drift speeds multiply by this much every minute.")]
public float rampPerMinute = 0.25f;
[Header("Feedback")]
public string adjustSfx = "click";
public string alarmSfx = "alarm";
public float alarmInterval = 2f;
[Header("Events")]
public UnityEvent<int, float> OnGaugeChanged; // (index, value)
public UnityEvent<int, bool> OnGaugeSafetyChanged; // (index, isSafe)
public UnityEvent<float> OnDangerChanged; // 0-1
public UnityEvent<float> OnRunEnded; // seconds survived
public UnityEvent OnAlarm;
public float Danger { get; private set; }
public float SurvivedSeconds { get; private set; }
public bool IsRunning { get; private set; } = true;
private float alarmTimer;
private bool[] wasSafe;
void Start()
{
wasSafe = new bool[gauges.Count];
for (int i = 0; i < gauges.Count; i++)
{
wasSafe[i] = gauges[i].InSafeBand;
OnGaugeChanged?.Invoke(i, gauges[i].value);
}
}
void Update()
{
if (!IsRunning) return;
SurvivedSeconds += Time.deltaTime;
float ramp = 1f + rampPerMinute * (SurvivedSeconds / 60f);
int unsafeCount = 0;
for (int i = 0; i < gauges.Count; i++)
{
var g = gauges[i];
float before = g.value;
g.value = Mathf.Clamp01(g.value + g.driftPerSecond * ramp * Time.deltaTime);
if (!Mathf.Approximately(before, g.value)) OnGaugeChanged?.Invoke(i, g.value);
bool safe = g.InSafeBand;
if (safe != wasSafe[i])
{
wasSafe[i] = safe;
OnGaugeSafetyChanged?.Invoke(i, safe);
}
if (!safe) unsafeCount++;
}
float dangerBefore = Danger;
Danger = unsafeCount > 0
? Mathf.Min(1f, Danger + dangerPerSecond * unsafeCount * Time.deltaTime)
: Mathf.Max(0f, Danger - dangerRecovery * Time.deltaTime);
if (!Mathf.Approximately(dangerBefore, Danger)) OnDangerChanged?.Invoke(Danger);
if (unsafeCount > 0)
{
alarmTimer -= Time.deltaTime;
if (alarmTimer <= 0f)
{
alarmTimer = alarmInterval;
if (!string.IsNullOrEmpty(alarmSfx)) AudioManager.Instance?.PlaySFX(alarmSfx);
OnAlarm?.Invoke();
}
}
else alarmTimer = 0f;
if (Danger >= 1f) EndRun();
}
/// <summary>Wire a cockpit Interactable's OnInteract here with the gauge's index.</summary>
public void Adjust(int index)
{
if (!IsRunning || index < 0 || index >= gauges.Count) return;
var g = gauges[index];
float direction = g.adjustOpposesDrift ? -Mathf.Sign(g.driftPerSecond) : 1f;
g.value = Mathf.Clamp01(g.value + g.adjustAmount * direction);
if (!string.IsNullOrEmpty(adjustSfx)) AudioManager.Instance?.PlaySFX(adjustSfx);
OnGaugeChanged?.Invoke(index, g.value);
}
public void EndRun()
{
if (!IsRunning) return;
IsRunning = false;
OnRunEnded?.Invoke(SurvivedSeconds);
GameManager.Instance?.GameOver();
}
public void Restart()
{
Danger = 0f;
SurvivedSeconds = 0f;
IsRunning = true;
for (int i = 0; i < gauges.Count; i++)
{
gauges[i].value = (gauges[i].safeMin + gauges[i].safeMax) * 0.5f;
OnGaugeChanged?.Invoke(i, gauges[i].value);
}
OnDangerChanged?.Invoke(0f);
}
}