using System; using System.Collections.Generic; using UnityEngine; namespace ProjectI.Settings { [Serializable] public sealed class PlayerSettingsRecord { public int Version = 2; public float HipFieldOfView = 80; public float SensitivityMultiplier = 1; public bool VSyncEnabled = true; public int FrameRateLimit = 120; public float DamageFlashStrength = .35f; public string MonitorKey = ""; public int WindowMode = 0; } public static class LocalPlayerSettings { private const string Key = "ProjectI.LocalSettings.v1"; private static PlayerSettingsRecord current; private static readonly HashSet blockers = new HashSet(); public static float HipFieldOfView { get { EnsureLoaded(); return current.HipFieldOfView; } } public static float SensitivityMultiplier { get { EnsureLoaded(); return current.SensitivityMultiplier; } } public static bool VSyncEnabled { get { EnsureLoaded(); return current.VSyncEnabled; } } public static int FrameRateLimit { get { EnsureLoaded(); return current.FrameRateLimit; } } public static float DamageFlashStrength { get { EnsureLoaded(); return current.DamageFlashStrength; } } public static string MonitorKey { get { EnsureLoaded(); return current.MonitorKey; } } public static int WindowMode { get { EnsureLoaded(); return current.WindowMode; } } public static bool IsInputBlocked => blockers.Count > 0; #if UNITY_EDITOR || DEVELOPMENT_BUILD public static string InputBlockerSummary { get { var names = new System.Collections.Generic.List(); foreach (var owner in blockers) names.Add(owner.GetType().Name); return string.Join(", ", names); } } #endif public static event Action Changed; public static event Action DamagePreviewRequested; public static PlayerSettingsRecord Validate(PlayerSettingsRecord value) { if (value == null || value.Version < 1 || value.Version > 2) return new PlayerSettingsRecord(); int frameRateLimit = value.FrameRateLimit == 0 ? 0 : Mathf.Clamp(value.FrameRateLimit, 30, 360); return new PlayerSettingsRecord { HipFieldOfView = Finite(value.HipFieldOfView) ? Mathf.Clamp(value.HipFieldOfView, 60, 100) : 80, SensitivityMultiplier = Finite(value.SensitivityMultiplier) ? Mathf.Clamp(value.SensitivityMultiplier, .25f, 3) : 1, VSyncEnabled = value.VSyncEnabled, FrameRateLimit = frameRateLimit, DamageFlashStrength = value.Version == 1 ? .35f : Finite(value.DamageFlashStrength) ? Mathf.Clamp01(value.DamageFlashStrength) : .35f, MonitorKey = value.MonitorKey ?? "", WindowMode = value.WindowMode == 1 ? 1 : 0 }; } private static bool Finite(float value) => !float.IsNaN(value) && !float.IsInfinity(value); private static void EnsureLoaded() { if (current != null) return; try { current = Validate(JsonUtility.FromJson(PlayerPrefs.GetString(Key, ""))); } catch { current = new PlayerSettingsRecord(); } } public static void Preview(float fov, float sensitivity) { EnsureLoaded(); Preview(fov, sensitivity, current.VSyncEnabled, current.FrameRateLimit); } public static void Preview(float fov, float sensitivity, bool vSyncEnabled, int frameRateLimit) { EnsureLoaded(); current = Validate(new PlayerSettingsRecord { HipFieldOfView = fov, SensitivityMultiplier = sensitivity, VSyncEnabled = vSyncEnabled, FrameRateLimit = frameRateLimit, DamageFlashStrength = current.DamageFlashStrength, MonitorKey = current.MonitorKey, WindowMode = current.WindowMode }); ApplyDisplaySettings(); Changed?.Invoke(); } public static void PreviewDamageFlash(float strength) { EnsureLoaded(); current.DamageFlashStrength = Finite(strength) ? Mathf.Clamp01(strength) : .35f; Changed?.Invoke(); } public static void PreviewDamage() => DamagePreviewRequested?.Invoke(); public static void SetWindowPreference(string monitorKey, int mode) { EnsureLoaded(); current.MonitorKey = monitorKey ?? ""; current.WindowMode = mode == 1 ? 1 : 0; Changed?.Invoke(); } public static void RestoreDefaults() { current = new PlayerSettingsRecord(); ApplyDisplaySettings(); Changed?.Invoke(); } public static void ApplyDisplaySettings() { EnsureLoaded(); QualitySettings.vSyncCount = current.VSyncEnabled ? 1 : 0; Application.targetFrameRate = current.VSyncEnabled || current.FrameRateLimit == 0 ? -1 : current.FrameRateLimit; } public static void Save() { EnsureLoaded(); PlayerPrefs.SetString(Key, JsonUtility.ToJson(current)); PlayerPrefs.Save(); } public static void SetInputBlocked(object owner, bool blocked) { if (owner == null) return; if (blocked) blockers.Add(owner); else blockers.Remove(owner); } [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void ResetRuntime() { current = null; blockers.Clear(); Changed = null; DamagePreviewRequested = null; } [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] private static void ApplySettingsAtStartup() { EnsureLoaded(); ApplyDisplaySettings(); } } }