using UnityEngine;
/// <summary>
/// GAME 1303 Tier 4 — Saves which levels are cleared, best times, and best collectible
/// counts. Uses PlayerPrefs, so it works with zero setup and survives quitting the game.
///
/// There is no component to add — it's a static helper any script can call:
/// LevelProgress.MarkCleared("sector1_level1");
/// LevelProgress.IsCleared(id);
/// LevelProgress.RecordTime(id, 42.3f); // keeps the best
/// LevelProgress.GetBestTime(id); // -1 if never finished
/// LevelProgress.IsUnlocked(levelData, levelSet);
/// LevelProgress.ResetAll(); // a "clear save" button
///
/// Tier 7's SaveSystem can replace this with a JSON file later; keep the same method
/// names and nothing else has to change.
///
/// NOTE FOR CLASS: PlayerPrefs lives in the registry / a plist per project name. Two
/// students sharing a machine share it, and it does NOT reset when you rebuild — that's
/// usually the answer to "why is level 3 still unlocked?"
/// </summary>
public static class LevelProgress
{
const string ClearedKey = "cleared_";
const string TimeKey = "besttime_";
const string CollectKey = "bestitems_";
const string UnlockKey = "unlocked_";
// ---- Cleared ----
public static void MarkCleared(string levelId)
{
if (string.IsNullOrEmpty(levelId)) return;
PlayerPrefs.SetInt(ClearedKey + levelId, 1);
PlayerPrefs.Save();
}
public static bool IsCleared(string levelId) =>
!string.IsNullOrEmpty(levelId) && PlayerPrefs.GetInt(ClearedKey + levelId, 0) == 1;
// ---- Best time (seconds; lower is better) ----
public static void RecordTime(string levelId, float seconds)
{
if (string.IsNullOrEmpty(levelId) || seconds <= 0f) return;
float best = GetBestTime(levelId);
if (best < 0f || seconds < best)
{
PlayerPrefs.SetFloat(TimeKey + levelId, seconds);
PlayerPrefs.Save();
}
}
/// <summary>Best time in seconds, or -1 if there isn't one.</summary>
public static float GetBestTime(string levelId) =>
string.IsNullOrEmpty(levelId) ? -1f : PlayerPrefs.GetFloat(TimeKey + levelId, -1f);
public static bool IsNewRecord(string levelId, float seconds)
{
float best = GetBestTime(levelId);
return best < 0f || seconds < best;
}
// ---- Best collectibles ----
public static void RecordCollectibles(string levelId, int count)
{
if (string.IsNullOrEmpty(levelId)) return;
if (count > GetBestCollectibles(levelId))
{
PlayerPrefs.SetInt(CollectKey + levelId, count);
PlayerPrefs.Save();
}
}
public static int GetBestCollectibles(string levelId) =>
string.IsNullOrEmpty(levelId) ? 0 : PlayerPrefs.GetInt(CollectKey + levelId, 0);
// ---- Purchased unlocks (spent currency) ----
public static void MarkUnlocked(string levelId)
{
PlayerPrefs.SetInt(UnlockKey + levelId, 1);
PlayerPrefs.Save();
}
public static bool WasPurchased(string levelId) => PlayerPrefs.GetInt(UnlockKey + levelId, 0) == 1;
// ---- The unlock rule ----
/// <summary>
/// A level is playable if: it's marked unlocked from start, OR it was purchased before,
/// OR every level in requiresCleared is cleared (when the list is empty: the previous
/// level in the set is cleared), AND any unlock cost has been paid.
/// </summary>
public static bool IsUnlocked(LevelData level, LevelSet set = null)
{
if (level == null) return false;
if (level.unlockedFromStart) return true;
if (WasPurchased(level.levelId)) return true;
// Explicit prerequisites
if (level.requiresCleared != null && level.requiresCleared.Length > 0)
{
foreach (var id in level.requiresCleared)
if (!IsCleared(id)) return false;
return level.unlockCost <= 0; // still needs paying if it costs something
}
// Default: the previous level in the set
if (set != null)
{
int i = set.levels.IndexOf(level);
if (i <= 0) return level.unlockCost <= 0;
var prev = set.levels[i - 1];
if (prev == null || !IsCleared(prev.levelId)) return false;
return level.unlockCost <= 0;
}
return level.unlockCost <= 0;
}
/// <summary>Try to pay a level's unlock cost out of the CollectionTracker.</summary>
public static bool TryPurchase(LevelData level)
{
if (level == null || level.unlockCost <= 0 || level.unlockCurrency == null) return false;
if (CollectionTracker.Instance == null) return false;
if (!CollectionTracker.Instance.Spend(level.unlockCurrency, level.unlockCost)) return false;
MarkUnlocked(level.levelId);
return true;
}
// ---- Housekeeping ----
public static void ResetAll()
{
PlayerPrefs.DeleteAll();
PlayerPrefs.Save();
Debug.Log("LevelProgress: all saved progress cleared.");
}
public static void ResetLevel(string levelId)
{
PlayerPrefs.DeleteKey(ClearedKey + levelId);
PlayerPrefs.DeleteKey(TimeKey + levelId);
PlayerPrefs.DeleteKey(CollectKey + levelId);
PlayerPrefs.DeleteKey(UnlockKey + levelId);
PlayerPrefs.Save();
}
}