Files

228 lines
7.8 KiB
C#

using System;
using System.IO;
using BriarQueen.Data.Identifiers;
using BriarQueen.Data.IO;
using BriarQueen.Framework.Coordinators.Events;
using BriarQueen.Framework.Events.Save;
using BriarQueen.Framework.Managers.Audio;
using BriarQueen.Framework.Services.Settings.Data;
using Cysharp.Threading.Tasks;
using Newtonsoft.Json;
using UnityEngine;
using VContainer;
using AudioSettings = BriarQueen.Framework.Services.Settings.Data.AudioSettings;
namespace BriarQueen.Framework.Services.Settings
{
public class SettingsService
{
private const string AUDIO_FILE = "Audio.json";
private const string VISUAL_FILE = "Visual.json";
private const string GAME_FILE = "Game.json";
private readonly AudioManager _audioManager;
private readonly EventCoordinator _eventCoordinator;
[Inject]
public SettingsService(EventCoordinator eventCoordinator, AudioManager audioManager)
{
_eventCoordinator = eventCoordinator;
_audioManager = audioManager;
}
public AudioSettings Audio { get; private set; }
public VisualSettings Visual { get; private set; }
public GameSettings Game { get; private set; }
public async UniTask InitializeAsync()
{
#if UNITY_EDITOR
Debug.Log($"[{nameof(SettingsService)}] Initializing...");
#endif
EnsureDirectories();
await LoadAllSettings();
Apply(Audio, Visual, Game, false, false);
#if UNITY_EDITOR
Debug.Log($"[{nameof(SettingsService)}] Initialized.");
#endif
}
private void EnsureDirectories()
{
Directory.CreateDirectory(FilePaths.ConfigFolder);
Directory.CreateDirectory(FilePaths.SaveDataFolder);
Directory.CreateDirectory(FilePaths.SaveBackupDataFolder);
}
public void Apply(
AudioSettings audio,
VisualSettings visual,
GameSettings game,
bool saveToDisk = true,
bool fireEvent = true)
{
Audio = audio ?? new AudioSettings();
Visual = visual ?? new VisualSettings();
Game = game ?? new GameSettings();
ApplyAudio(Audio);
ApplyVisual(Visual);
ApplyGame(Game);
if (saveToDisk)
{
SaveSettingsFile(Audio, AUDIO_FILE).Forget();
SaveSettingsFile(Visual, VISUAL_FILE).Forget();
SaveSettingsFile(Game, GAME_FILE).Forget();
}
if (fireEvent)
_eventCoordinator.Publish(new SettingsChangedEvent());
}
public float GetVolume(string mixerGroup)
{
return mixerGroup switch
{
AudioMixerGroups.MASTER_GROUP => Audio.MasterVolume,
AudioMixerGroups.MUSIC_GROUP => Audio.MusicVolume,
AudioMixerGroups.SFX_GROUP => Audio.SfxVolume,
AudioMixerGroups.UI_GROUP => Audio.UIVolume,
AudioMixerGroups.VOICE_GROUP => Audio.VoiceVolume,
_ => Audio.MasterVolume
};
}
public bool AreTutorialsEnabled()
{
return Game != null && Game.TutorialsEnabled;
}
public bool AreTooltipsEnabled()
{
return Game != null && Game.TooltipsEnabled;
}
private void ApplyAudio(AudioSettings a)
{
_audioManager.SetVolume(AudioMixerParameters.MASTER_VOLUME, a.MasterVolume);
_audioManager.SetVolume(AudioMixerParameters.MUSIC_VOLUME, a.MusicVolume);
_audioManager.SetVolume(AudioMixerParameters.SFX_VOLUME, a.SfxVolume);
_audioManager.SetVolume(AudioMixerParameters.VOICE_VOLUME, a.VoiceVolume);
_audioManager.SetVolume(AudioMixerParameters.AMBIENCE_VOLUME, a.AmbienceVolume);
_audioManager.SetVolume(AudioMixerParameters.UI_VOLUME, a.UIVolume);
Application.runInBackground = !a.MuteWhenUnfocused;
}
private void ApplyVisual(VisualSettings v)
{
SanitizeVisualSettings(v);
var currentRes = Screen.currentResolution;
var rr = new RefreshRate
{
numerator = currentRes.refreshRateRatio.numerator,
denominator = currentRes.refreshRateRatio.denominator
};
Screen.SetResolution(currentRes.width, currentRes.height, v.FullScreenMode, rr);
QualitySettings.vSyncCount = v.VSyncCount;
// On many desktop platforms, VSync will take precedence over targetFrameRate.
Application.targetFrameRate = v.MaxFramerate;
}
private void ApplyGame(GameSettings g)
{
if (g.PopupDisplayDuration <= 0f)
g.PopupDisplayDuration = 3f;
// TutorialsEnabled and TooltipsEnabled are gameplay/UI preferences.
// They do not need an immediate engine-level apply step here.
// Systems that show tutorials/tooltips should query SettingsManager.Game
// or use the helper methods above.
}
private async UniTask LoadAllSettings()
{
Audio = await LoadOrCreateSettingsFiles<AudioSettings>(AUDIO_FILE);
Visual = await LoadOrCreateSettingsFiles<VisualSettings>(VISUAL_FILE);
Game = await LoadOrCreateSettingsFiles<GameSettings>(GAME_FILE);
Audio ??= new AudioSettings();
Visual ??= new VisualSettings();
Game ??= new GameSettings();
SanitizeVisualSettings(Visual);
if (Game.PopupDisplayDuration <= 0f)
Game.PopupDisplayDuration = 3f;
}
private void SanitizeVisualSettings(VisualSettings v)
{
if (v == null)
return;
v.VSyncCount = Mathf.Clamp(v.VSyncCount, 0, 2);
// Treat 0 or any negative value other than -1 as invalid and fall back to 120.
// If you want to support -1 as "platform default", keep it. Otherwise, normalize it too.
if (v.MaxFramerate == 0 || v.MaxFramerate < -1)
v.MaxFramerate = 120;
if (v.MaxFramerate > 0)
v.MaxFramerate = Mathf.Clamp(v.MaxFramerate, 30, 240);
}
private async UniTask<T> LoadOrCreateSettingsFiles<T>(string fileName) where T : class, new()
{
var filePath = Path.Combine(FilePaths.ConfigFolder, fileName);
T settings;
if (File.Exists(filePath))
{
try
{
var jsonString = await File.ReadAllTextAsync(filePath);
settings = JsonConvert.DeserializeObject<T>(jsonString);
}
catch (Exception e)
{
Debug.LogError($"[SettingsManager] Failed to load {fileName}. Creating new. Error: {e.Message}");
settings = new T();
await SaveSettingsFile(settings, fileName);
}
}
else
{
settings = new T();
await SaveSettingsFile(settings, fileName);
}
return settings ?? new T();
}
private async UniTask SaveSettingsFile<T>(T settings, string fileName) where T : class
{
if (settings == null)
return;
var filePath = Path.Combine(FilePaths.ConfigFolder, fileName);
Directory.CreateDirectory(FilePaths.ConfigFolder);
try
{
var jsonString = JsonConvert.SerializeObject(settings, Formatting.Indented);
await File.WriteAllTextAsync(filePath, jsonString);
}
catch (Exception e)
{
Debug.LogError($"[SettingsManager] Failed to save {fileName}. Error: {e.Message}");
}
}
}
}