using System;
using System.IO;
using UnityEngine;
namespace ProjectM.Client
{
///
/// Client-local settings persistence + application. Loads settings.json from
/// Application.persistentDataPath at boot () and applies
/// Graphics (Screen / QualitySettings / targetFrameRate) + Audio (AudioListener.volume = Master;
/// Music/Sfx bus trims). The single source of truth for the UITK
/// SettingsScreen shared by the main menu + the in-game pause overlay. Saves are atomic
/// (temp file + File.Replace). JsonUtility keeps it asmdef-ref-free. Never replicated.
///
public static class SettingsService
{
public static GameSettings Current { get; private set; } = GameSettings.Defaults();
static string FilePath => Path.Combine(Application.persistentDataPath, "settings.json");
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void Boot()
{
Load();
Apply(Current);
}
/// Read settings from disk (or defaults if absent/corrupt). Returns the loaded value.
public static GameSettings Load()
{
try
{
if (File.Exists(FilePath))
{
var json = File.ReadAllText(FilePath);
var s = JsonUtility.FromJson(json);
if (s.Version != GameSettings.CurrentVersion)
s = Migrate(s);
Current = s.Clamped();
}
else
{
Current = GameSettings.Defaults();
}
}
catch (Exception e)
{
Debug.LogWarning($"[SettingsService] Load failed ({e.Message}); using defaults.");
Current = GameSettings.Defaults();
}
return Current;
}
/// Clamp + version-stamp + atomically write to disk; updates .
public static void Save(GameSettings settings)
{
settings = settings.Clamped();
settings.Version = GameSettings.CurrentVersion;
Current = settings;
try
{
var json = JsonUtility.ToJson(settings, true);
var tmp = FilePath + ".tmp";
File.WriteAllText(tmp, json);
if (File.Exists(FilePath)) File.Replace(tmp, FilePath, null);
else File.Move(tmp, FilePath);
}
catch (Exception e)
{
Debug.LogWarning($"[SettingsService] Save failed: {e.Message}");
}
}
/// Apply settings to the live engine. Master rides AudioListener.volume; buses are per-call trims.
public static void Apply(GameSettings s)
{
s = s.Clamped();
// Graphics
QualitySettings.SetQualityLevel(s.QualityLevel, true);
QualitySettings.vSyncCount = s.VSync;
var mode = (FullScreenMode)s.FullScreenMode;
if (s.RefreshHz > 0)
Screen.SetResolution(s.ResWidth, s.ResHeight, mode, new RefreshRate { numerator = (uint)s.RefreshHz, denominator = 1 });
else
Screen.SetResolution(s.ResWidth, s.ResHeight, mode);
Application.targetFrameRate = s.TargetFps; // -1 = uncapped (ignored while vSync > 0)
// Audio
AudioListener.volume = s.Master;
GameVolume.Music = s.Music;
GameVolume.Sfx = s.Sfx;
}
/// Convenience for the settings UI: persist then apply.
public static void SaveAndApply(GameSettings s)
{
Save(s);
Apply(Current);
}
// Forward-compatible migration: fill from current defaults, preserve any recognizable old values.
// Additive-only as the schema grows (never throws on an unknown version).
static GameSettings Migrate(GameSettings old)
{
var def = GameSettings.Defaults();
if (old.ResWidth > 0) def.ResWidth = old.ResWidth;
if (old.ResHeight > 0) def.ResHeight = old.ResHeight;
if (old.Master > 0f) def.Master = old.Master;
if (old.Music > 0f) def.Music = old.Music;
if (old.Sfx > 0f) def.Sfx = old.Sfx;
def.Version = GameSettings.CurrentVersion;
return def;
}
}
}