using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; namespace Ironspore { public enum SettingsApplyResult { Saved, ConfirmDisplay, Failed } // Runtime settings orchestration; UI owns its draft, while the store owns durable snapshots. public sealed class SettingsService : IDisposable { readonly SettingsStore store; readonly GameSettingsData defaults; readonly float initialVolume; readonly int initialQuality; readonly Func clock; GameSettingsData current, preview; float displayDeadline; public InputBindingService Bindings { get; } public GameSettingsData Current => current.Copy(); public GameSettingsData Defaults => defaults.Copy(); public bool ConfirmingDisplay => preview != null; public int ConfirmationSeconds => Mathf.Max(0, Mathf.CeilToInt(displayDeadline - clock())); public string Error => store.Error; public float LookSensitivity => current.mouseSensitivity; public float AimSensitivity => current.aimSensitivity; public SettingsService(SettingsStore store, InputActionAsset input, GameSettingsData defaults, Func clock = null) { this.store = store; this.defaults = defaults.Copy(); this.clock = clock ?? (() => Time.realtimeSinceStartup); initialVolume = AudioListener.volume; initialQuality = QualitySettings.GetQualityLevel(); Bindings = new InputBindingService(input); current = store.Data.Normalize(QualitySettings.names.Length); if (!Bindings.TryLoad(current.bindingOverrides)) Debug.LogWarning("Invalid input overrides were ignored; using default controls."); current.bindingOverrides = Bindings.Save(); ApplyRuntime(current, true); } public static GameSettingsData RuntimeDefaults() => new GameSettingsData { qualityLevel = Mathf.Min(2, QualitySettings.names.Length - 1), resolutionWidth = Application.isEditor ? 1280 : Mathf.Max(640, Screen.width), resolutionHeight = Application.isEditor ? 720 : Mathf.Max(360, Screen.height), fullscreen = !Application.isEditor && Screen.fullScreen }; public List Resolutions() { var sizes = new List(); foreach (var resolution in Screen.resolutions) AddSize(sizes, resolution.width, resolution.height); AddSize(sizes, 1280, 720); AddSize(sizes, 1600, 900); AddSize(sizes, 1920, 1080); AddSize(sizes, current.resolutionWidth, current.resolutionHeight); sizes.Sort((a, b) => a.x == b.x ? a.y.CompareTo(b.y) : a.x.CompareTo(b.x)); return sizes; } static void AddSize(List sizes, int width, int height) { var size = new Vector2Int(width, height); if (width >= 640 && height >= 360 && !sizes.Contains(size)) sizes.Add(size); } public SettingsApplyResult Apply(GameSettingsData draft) { CancelDisplay(); var candidate = draft.Normalize(QualitySettings.names.Length); candidate.bindingOverrides = Bindings.Save(); if (!candidate.SameDisplay(current)) { preview = candidate; displayDeadline = clock() + 15; ApplyRuntime(preview, true); return SettingsApplyResult.ConfirmDisplay; } return Save(candidate) ? SettingsApplyResult.Saved : SettingsApplyResult.Failed; } public bool ConfirmDisplay() { if (preview == null) return false; var candidate = preview; preview = null; if (Save(candidate)) return true; ApplyRuntime(current, true); Bindings.TryLoad(current.bindingOverrides); return false; } bool Save(GameSettingsData candidate) { if (!store.Save(candidate)) return false; current = candidate.Copy(); ApplyRuntime(current, false); return true; } public bool Tick() { if (!ConfirmingDisplay || clock() < displayDeadline) return false; CancelDisplay(); return true; } public void CancelDisplay() { if (preview == null) return; preview = null; ApplyRuntime(current, true); } public void DiscardDraft() { Bindings.Cancel(); CancelDisplay(); Bindings.TryLoad(current.bindingOverrides); } void ApplyRuntime(GameSettingsData data, bool display) { AudioListener.volume = data.masterVolume; if (QualitySettings.GetQualityLevel() != data.qualityLevel) QualitySettings.SetQualityLevel(data.qualityLevel, true); if (display && !Application.isEditor) Screen.SetResolution(data.resolutionWidth, data.resolutionHeight, data.fullscreen ? FullScreenMode.ExclusiveFullScreen : FullScreenMode.Windowed); } public void Dispose() { DiscardDraft(); Bindings.Dispose(); #if UNITY_EDITOR AudioListener.volume = initialVolume; QualitySettings.SetQualityLevel(initialQuality, true); #endif } } }