using System; using System.IO; using UnityEngine; /// Shared JSON-backed store for player-facing game settings (/Settings.json). /// Loaded lazily and cached in memory, so per-frame reads never touch the disk; each change is /// written immediately (settings edits are rare), so a hard-killed build keeps them. The typed /// facades (MouseSensitivity, MasterVolume) own the ranges/clamping; this class only persists raw values. public static class GameSettings { private const string FileName = "Settings.json"; private const int FileVersion = 1; [Serializable] private class Data { public int version = FileVersion; public float mouseSensitivity = global::MouseSensitivity.Default; public float masterVolume = global::MasterVolume.Default; public string playerName = ""; // Host-side match rules (Esc-menu Match Panel). Ranges are authored on the panel sliders; the // values are applied server-side in MatchManager.StartMatch (with defensive clamps there). // Defaults mirror the Main scene's authored MatchManager values, so a fresh Settings.json // starts matches exactly like before the panel existed. public int matchMode = 0; // MatchManager.MatchMode index (0 = Classic, 1 = Conversion) public int matchHunterCount = 1; public float matchHideDuration = 120f; public float matchSeekDuration = 300f; public float matchResolveDuration = 50f; public float matchRadarPingInterval = 30f; public float matchRadarPingLifetime = 1f; } private static Data _data; private static Data Store => _data ??= Load(); private static string SavePath() => Path.Combine(Application.persistentDataPath, FileName); public static float MouseSensitivity { get => Store.mouseSensitivity; set { if (Mathf.Approximately(Store.mouseSensitivity, value)) return; Store.mouseSensitivity = value; Save(); } } public static float MasterVolume { get => Store.masterVolume; set { if (Mathf.Approximately(Store.masterVolume, value)) return; Store.masterVolume = value; Save(); } } /// Display name shown above the avatar. Empty = per-spawn fallback ("Player <clientId>"). public static string PlayerName { get => Store.playerName ?? ""; set { string v = value == null ? "" : value.Trim(); if (Store.playerName == v) return; Store.playerName = v; Save(); } } /// MatchManager.MatchMode enum index the host starts matches with (0 = Classic, 1 = Conversion). public static int MatchModeIndex { get => Store.matchMode; set { if (Store.matchMode != value) { Store.matchMode = value; Save(); } } } /// How many randomly chosen players become Hunters at match start. public static int MatchHunterCount { get => Store.matchHunterCount; set { if (Store.matchHunterCount != value) { Store.matchHunterCount = value; Save(); } } } /// HIDE phase length in seconds. public static float MatchHideDuration { get => Store.matchHideDuration; set { if (!Mathf.Approximately(Store.matchHideDuration, value)) { Store.matchHideDuration = value; Save(); } } } /// SEEK phase length in seconds. public static float MatchSeekDuration { get => Store.matchSeekDuration; set { if (!Mathf.Approximately(Store.matchSeekDuration, value)) { Store.matchSeekDuration = value; Save(); } } } /// RESOLUTION (result screen) phase length in seconds. public static float MatchResolveDuration { get => Store.matchResolveDuration; set { if (!Mathf.Approximately(Store.matchResolveDuration, value)) { Store.matchResolveDuration = value; Save(); } } } /// Seconds between radar ping sweeps during Seek (0 = pings disabled). public static float MatchRadarPingInterval { get => Store.matchRadarPingInterval; set { if (!Mathf.Approximately(Store.matchRadarPingInterval, value)) { Store.matchRadarPingInterval = value; Save(); } } } /// Seconds each hider radar blip stays alive before it fades. public static float MatchRadarPingLifetime { get => Store.matchRadarPingLifetime; set { if (!Mathf.Approximately(Store.matchRadarPingLifetime, value)) { Store.matchRadarPingLifetime = value; Save(); } } } private static Data Load() { string path = SavePath(); if (File.Exists(path)) { try { var data = JsonUtility.FromJson(File.ReadAllText(path)); if (data != null) return data; } catch (Exception e) { Debug.LogWarning($"[GameSettings] Loading {FileName} failed, using defaults: {e.Message}"); } } return new Data(); } private static void Save() { try { Directory.CreateDirectory(Application.persistentDataPath); File.WriteAllText(SavePath(), JsonUtility.ToJson(Store, true)); } catch (Exception e) { Debug.LogWarning($"[GameSettings] Saving {FileName} failed: {e.Message}"); } } }