First commit for private source control. Older commits available on Github.

This commit is contained in:
2026-03-26 12:52:52 +00:00
parent a04c602626
commit 2d449c4a17
2176 changed files with 408185 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2113896494844424af1d7aa9c3c01ae4
timeCreated: 1769705715

View File

@@ -0,0 +1,64 @@
using System;
using BriarQueen.Framework.Assets;
using Cysharp.Threading.Tasks;
using UnityEngine;
using VContainer;
using Object = UnityEngine.Object;
namespace BriarQueen.Framework.Services.Destruction
{
/// <summary>
/// Handles safe destruction of GameObjects and ensures proper cleanup from world state.
/// </summary>
public class DestructionService
{
private readonly AddressableManager _addressables;
[Inject]
public DestructionService(AddressableManager addressables)
{
_addressables = addressables;
}
/// <summary>
/// Asynchronously destroys a GameObject, invoking destructible callbacks and unregistering state.
/// </summary>
public async UniTask Destroy(GameObject go)
{
if (go == null) return;
Debug.Log($"[DestructionManager] Trying to destroy object of {go}");
var destructibles = go.GetComponentsInChildren<IDestructible>(true);
if (destructibles is { Length: > 0 })
{
foreach (var d in destructibles)
try
{
if (d != null) await d.OnPreDestroy();
}
catch (Exception ex)
{
Debug.LogWarning($"[DestructionManager] Exception in OnPreDestroy of {d}: {ex}");
}
foreach (var d in destructibles)
try
{
if (d != null) await d.OnDestroyed();
}
catch (Exception ex)
{
Debug.LogWarning($"[DestructionManager] Exception in OnDestroyed of {d}: {ex}");
}
}
// Prefer Addressables release if this is an Addressables-managed instance.
if (_addressables != null &&
_addressables.TryReleaseInstance(go)) return; // Addressables.ReleaseInstance will destroy it.
Object.Destroy(go);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0fcef8bcf5944f639d5d3daab61778c3
timeCreated: 1769705715

View File

@@ -0,0 +1,10 @@
using Cysharp.Threading.Tasks;
namespace BriarQueen.Framework.Services.Destruction
{
public interface IDestructible
{
UniTask OnPreDestroy(); // Optional: prepare for destruction
UniTask OnDestroyed(); // Final cleanup
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ab93d57d135a4580afcfd67996a0348b
timeCreated: 1769705764

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 41b2559a70e741e1961c7a685eaa788e
timeCreated: 1769705047

Binary file not shown.

View File

@@ -0,0 +1,226 @@
using System;
using BriarQueen.Data.Identifiers;
using BriarQueen.Framework.Assets;
using BriarQueen.Framework.Coordinators.Events;
using BriarQueen.Framework.Events.UI;
using BriarQueen.Framework.Managers.IO;
using BriarQueen.Framework.Managers.Levels;
using BriarQueen.Framework.Registries;
using Cysharp.Threading.Tasks;
using UnityEditor;
using UnityEngine;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement;
using VContainer;
namespace BriarQueen.Framework.Services.Game
{
public class GameService
{
private readonly AddressableManager _addressableManager;
private readonly AssetRegistry _assetRegistry;
private readonly EventCoordinator _eventCoordinator;
private readonly LevelManager _levelManager;
private readonly SaveManager _saveManager;
private AsyncOperationHandle<SceneInstance> _gameSceneHandle;
private AsyncOperationHandle<SceneInstance> _mainMenuSceneHandle;
private AsyncOperationHandle<SceneInstance>? _uiSceneHandle;
public bool IsMainMenuSceneLoaded {get; private set;}
[Inject]
public GameService(
SaveManager saveManager,
EventCoordinator eventCoordinator,
AddressableManager addressableManager,
LevelManager levelManager,
AssetRegistry assetRegistry)
{
_saveManager = saveManager;
_eventCoordinator = eventCoordinator;
_addressableManager = addressableManager;
_levelManager = levelManager;
_assetRegistry = assetRegistry;
}
public async UniTask LoadUIAndMainMenuScene()
{
if (_assetRegistry == null ||
!_assetRegistry.TryGetReference(AssetKeyIdentifiers.Get(SceneKey.UIScene), out var uiSceneRef))
{
Debug.LogError("[GameService] UI Scene reference not found in registry.");
return;
}
_uiSceneHandle = await _addressableManager.LoadSceneAsync(uiSceneRef);
if (!_uiSceneHandle.Value.IsValid())
{
Debug.LogError("[GameService] Failed to load UI Scene.");
return;
}
// After UI is loaded, we fade to black to prevent pops
_eventCoordinator.PublishImmediate(new FadeEvent(false, 0.2f));
await UniTask.Delay(TimeSpan.FromSeconds(0.5f));
await UniTask.NextFrame();
await LoadMainMenu();
}
public async UniTask LoadMainMenu()
{
const float fadeDuration = 1f;
_eventCoordinator.PublishImmediate(new FadeEvent(false, fadeDuration));
await UniTask.Delay(TimeSpan.FromSeconds(fadeDuration));
await UnloadGameSceneIfLoaded();
if (_assetRegistry == null ||
!_assetRegistry.TryGetReference(
AssetKeyIdentifiers.Get(SceneKey.MainMenuScene),
out var mainMenuSceneRef))
{
Debug.LogError("[GameService] Main menu scene reference missing.");
return;
}
_mainMenuSceneHandle = await _addressableManager.LoadSceneAsync(mainMenuSceneRef);
if (!_mainMenuSceneHandle.IsValid())
{
Debug.LogError("[GameService] Failed to load Main Menu scene.");
return;
}
SceneManager.SetActiveScene(_mainMenuSceneHandle.Result.Scene);
IsMainMenuSceneLoaded = true;
await UniTask.NextFrame();
_eventCoordinator.PublishImmediate(new FadeEvent(true, fadeDuration));
}
public async UniTask StartGame()
{
await EnterGameplayLoop();
}
private async UniTask EnterGameplayLoop()
{
_eventCoordinator.PublishImmediate(new RequestUIOverrideEvent(false));
_eventCoordinator.PublishImmediate(new FadeEvent(false, 0.35f));
if (_mainMenuSceneHandle.IsValid())
{
await _addressableManager.UnloadSceneAsync(_mainMenuSceneHandle);
IsMainMenuSceneLoaded = false;
}
var currentSave = _saveManager.CurrentSave;
if (currentSave == null)
{
Debug.LogError("[GameService] Cannot start game because CurrentSave is null.");
await LoadMainMenu();
return;
}
Debug.Log($"[GameService] Loading scene '{currentSave.CurrentSceneID}'...");
if (!currentSave.OpeningCinematicPlayed)
{
if (_assetRegistry == null ||
!_assetRegistry.TryGetReference(
AssetKeyIdentifiers.Get(SceneKey.OpeningCinematicScene),
out var cinematicScene))
{
Debug.LogError("[GameService] Opening cinematic scene reference missing.");
await LoadMainMenu();
return;
}
var loadedSceneHandle = await _addressableManager.LoadSceneAsync(cinematicScene);
if (!loadedSceneHandle.IsValid())
{
Debug.LogError("[GameService] Failed to load opening cinematic scene.");
await LoadMainMenu();
return;
}
await SwapGameSceneHandle(loadedSceneHandle);
}
else
{
if (_assetRegistry == null ||
!_assetRegistry.TryGetReference(currentSave.CurrentSceneID, out var chapterSceneRef))
{
Debug.LogError($"[GameService] Scene reference missing for '{currentSave.CurrentSceneID}'.");
await LoadMainMenu();
return;
}
Debug.Log($"[GameService] Loading chapter scene '{currentSave.CurrentSceneID}'...");
var loadedSceneHandle = await _addressableManager.LoadSceneAsync(chapterSceneRef);
if (!loadedSceneHandle.IsValid())
{
Debug.LogError($"[GameService] Failed to load chapter scene '{currentSave.CurrentSceneID}'.");
await LoadMainMenu();
return;
}
await SwapGameSceneHandle(loadedSceneHandle);
var levelLoaded = await _levelManager.LoadLevel(currentSave.CurrentLevelID);
if (!levelLoaded)
{
Debug.LogError(
$"[GameService] Failed to load level '{currentSave.CurrentLevelID}'. Returning to main menu.");
await LoadMainMenu();
return;
}
}
_eventCoordinator.PublishImmediate(new FadeEvent(true, 0.35f));
}
public async UniTask UnloadGameSceneIfLoaded()
{
if (_gameSceneHandle.IsValid())
{
await _addressableManager.UnloadSceneAsync(_gameSceneHandle);
_gameSceneHandle = default;
}
}
public async UniTask SwapGameSceneHandle(AsyncOperationHandle<SceneInstance> nextSceneHandle)
{
if (!nextSceneHandle.IsValid())
{
Debug.LogError("[GameService] SwapGameSceneHandle called with invalid nextSceneHandle.");
return;
}
if (_gameSceneHandle.IsValid() && _gameSceneHandle.Equals(nextSceneHandle))
{
_gameSceneHandle = nextSceneHandle;
return;
}
if (_gameSceneHandle.IsValid())
await _addressableManager.UnloadSceneAsync(_gameSceneHandle);
_gameSceneHandle = nextSceneHandle;
SceneManager.SetActiveScene(nextSceneHandle.Result.Scene);
}
public void QuitGame()
{
#if UNITY_EDITOR
EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ef88df0f91964cd1a879ddaf98aca27b
timeCreated: 1769705047

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e1aa05d44324413fb6fff7d6dd1c8c55
timeCreated: 1769720489

Binary file not shown.

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 833a26bf9c2c446e8c738a8b377956a0
timeCreated: 1773830388

View File

@@ -0,0 +1,50 @@
// ==============================
// PuzzleBase.cs (updated)
// ==============================
using System.Collections.Generic;
using BriarQueen.Framework.Assets;
using BriarQueen.Framework.Coordinators.Events;
using BriarQueen.Framework.Managers.Audio;
using BriarQueen.Framework.Managers.Hints.Data;
using BriarQueen.Framework.Managers.IO;
using BriarQueen.Framework.Managers.Levels.Data;
using BriarQueen.Framework.Registries;
using Cysharp.Threading.Tasks;
using VContainer;
namespace BriarQueen.Framework.Services.Puzzles.Base
{
public abstract class BasePuzzle : BaseLevel
{
protected AddressableManager AddressableManager;
protected AssetRegistry AssetRegistry;
protected AudioManager AudioManager;
protected ItemRegistry ItemRegistry;
protected PuzzleService PuzzleService;
public abstract string PuzzleID { get; }
public override bool IsPuzzleLevel => true;
// BaseLevel still requires these.
public abstract override string LevelName { get; }
public abstract override Dictionary<int, BaseHint> Hints { get; }
public abstract UniTask CompletePuzzle();
[Inject]
public void Construct(EventCoordinator eventCoordinator, AudioManager audioManager,
SaveManager saveManager, ItemRegistry itemRegistry, AddressableManager addressableManager,
AssetRegistry assetRegistry, PuzzleService puzzleService)
{
EventCoordinator = eventCoordinator;
AudioManager = audioManager;
SaveManager = saveManager;
ItemRegistry = itemRegistry;
AddressableManager = addressableManager;
AssetRegistry = assetRegistry;
PuzzleService = puzzleService;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2f7dc618c5084940a094037aab5f4e8b
timeCreated: 1769876583

View File

@@ -0,0 +1,15 @@
using Cysharp.Threading.Tasks;
namespace BriarQueen.Framework.Services.Puzzles.Base
{
public interface IPuzzleStateful
{
/// <summary>If true, manager can mark the puzzle completed (and optionally clear state).</summary>
bool IsCompleted { get; }
UniTask<byte[]> CaptureState();
/// <summary>Restore the puzzle from a previously captured serialized blob.</summary>
UniTask RestoreState(byte[] state);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e05bb678cdfe40f6acffd77fceb3faba
timeCreated: 1769878647

View File

@@ -0,0 +1,7 @@
namespace BriarQueen.Framework.Services.Puzzles.Base
{
public interface IPuzzleWorldStateSync
{
void SyncWorldStateToSave();
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 120766eb18204d1593aad20a292f305c
timeCreated: 1774453311

View File

@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
using BriarQueen.Data.IO.Saves;
using BriarQueen.Framework.Managers.IO;
using BriarQueen.Framework.Services.Puzzles.Base;
using Cysharp.Threading.Tasks;
using UnityEngine;
using VContainer;
namespace BriarQueen.Framework.Services.Puzzles
{
public class PuzzleService : IDisposable
{
private readonly SaveManager _saveManager;
private BasePuzzle _currentBasePuzzle;
private bool _isWritingState;
[Inject]
public PuzzleService(SaveManager saveManager)
{
_saveManager = saveManager;
_saveManager.OnBeforeSaveRequestedAsync += OnBeforeSaveRequestedAsync;
}
public void Dispose()
{
_saveManager.OnBeforeSaveRequestedAsync -= OnBeforeSaveRequestedAsync;
}
public async UniTask LoadPuzzle(BasePuzzle basePuzzle)
{
_currentBasePuzzle = basePuzzle;
if (_currentBasePuzzle == null)
return;
await TryRestorePuzzleState(_currentBasePuzzle);
}
public async UniTask SavePuzzle(BasePuzzle basePuzzle)
{
if (basePuzzle == null)
return;
if (_currentBasePuzzle != null && basePuzzle != _currentBasePuzzle)
return;
await SavePuzzleState(basePuzzle, flushToDisk: true);
_currentBasePuzzle = null;
}
private async UniTask OnBeforeSaveRequestedAsync()
{
if (_currentBasePuzzle == null)
return;
await SavePuzzleState(_currentBasePuzzle, flushToDisk: false);
}
private async UniTask TryRestorePuzzleState(BasePuzzle basePuzzle)
{
if (basePuzzle == null || _saveManager.CurrentSave == null)
return;
if (basePuzzle is not IPuzzleStateful stateful)
return;
var save = _saveManager.CurrentSave;
var entry = save.PuzzleStates?.FirstOrDefault(x => x != null && x.PuzzleID == basePuzzle.PuzzleID);
var stateBlob = entry?.State;
try
{
await stateful.RestoreState(stateBlob);
}
catch (Exception ex)
{
Debug.LogWarning($"[PuzzleService] Failed to restore state for '{basePuzzle.PuzzleID}': {ex}");
}
}
private async UniTask SavePuzzleState(BasePuzzle basePuzzle, bool flushToDisk)
{
if (basePuzzle == null || _saveManager.CurrentSave == null)
return;
if (basePuzzle is not IPuzzleStateful stateful)
return;
if (_isWritingState)
return;
_isWritingState = true;
try
{
if (basePuzzle is IPuzzleWorldStateSync worldStateSync)
worldStateSync.SyncWorldStateToSave();
var save = _saveManager.CurrentSave;
save.PuzzleStates ??= new List<PuzzleStateSaveData>();
byte[] blob;
try
{
blob = await stateful.CaptureState() ?? Array.Empty<byte>();
}
catch (Exception ex)
{
Debug.LogWarning($"[PuzzleService] Failed to capture state for '{basePuzzle.PuzzleID}': {ex}");
return;
}
var existing = save.PuzzleStates.FirstOrDefault(x => x != null && x.PuzzleID == basePuzzle.PuzzleID);
if (existing == null)
{
existing = new PuzzleStateSaveData { PuzzleID = basePuzzle.PuzzleID };
save.PuzzleStates.Add(existing);
}
existing.State = blob;
existing.Completed = stateful.IsCompleted;
if (flushToDisk)
await _saveManager.SaveGameDataLatest();
}
finally
{
_isWritingState = false;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6475266033724d2db5366ba6e56cf8a1
timeCreated: 1769720489

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 793fe8757aa34b23a1249ffdafc4dba3
timeCreated: 1769800626

Binary file not shown.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b0c389726255940069bd3d699d642044
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,27 @@
using System;
namespace BriarQueen.Framework.Services.Settings.Data
{
[Serializable]
public class AudioSettings
{
public float MasterVolume;
public float MusicVolume;
public float SfxVolume;
public float VoiceVolume;
public float AmbienceVolume;
public float UIVolume;
public bool MuteWhenUnfocused;
public AudioSettings()
{
MasterVolume = 1.0f; // 100%
MusicVolume = 0.75f; // 75%
SfxVolume = 0.75f; // 75%
VoiceVolume = 1.0f; // 100%
AmbienceVolume = 0.75f; // 75%
UIVolume = 0.5f; // 50%
MuteWhenUnfocused = false;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f10f93ec9d8c48d18d41fcf1b8e2a160
timeCreated: 1769800676

View File

@@ -0,0 +1,13 @@
using System;
namespace BriarQueen.Framework.Services.Settings.Data
{
[Serializable]
public class GameSettings
{
public float PopupDisplayDuration = 3f;
public bool TutorialsEnabled = true;
public bool TooltipsEnabled = true;
public bool AutoUseTools = false;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c89e0d84df0742999d88e7a33e8360c2
timeCreated: 1772819897

View File

@@ -0,0 +1,27 @@
using System;
using UnityEngine;
namespace BriarQueen.Framework.Services.Settings.Data
{
[Serializable]
public class VisualSettings
{
public int VSyncCount; // 0 = Off, 1 = Every V-Blank, 2 = Every second V-Blank
public FullScreenMode FullScreenMode;
/// <summary>
/// Requested application framerate cap.
/// Ignored or effectively overridden on many platforms when VSync is enabled.
/// Use -1 for platform default / uncapped behavior.
/// </summary>
public int MaxFramerate;
public VisualSettings()
{
VSyncCount = 0;
FullScreenMode = FullScreenMode.FullScreenWindow;
MaxFramerate = 120;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6e288ada3e7b4b06a23bf0824f393543
timeCreated: 1769800769

View File

@@ -0,0 +1,228 @@
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}");
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6a9b8ee2440445a692de5a8e3851712c
timeCreated: 1769800626

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f74655d5e387472f83a5ada14d07c739
timeCreated: 1773945075

View File

@@ -0,0 +1,47 @@
using BriarQueen.Data.Identifiers;
using BriarQueen.Framework.Coordinators.Events;
using BriarQueen.Framework.Events.Save;
using BriarQueen.Framework.Events.UI;
using BriarQueen.Framework.Managers.IO;
using BriarQueen.Framework.Services.Settings;
using VContainer;
namespace BriarQueen.Framework.Services.Tutorials
{
public class TutorialService
{
private readonly EventCoordinator _eventCoordinator;
private readonly SettingsService _settingsService;
private readonly SaveManager _saveManager;
[Inject]
public TutorialService(
EventCoordinator eventCoordinator,
SettingsService settingsService,
SaveManager saveManager)
{
_eventCoordinator = eventCoordinator;
_settingsService = settingsService;
_saveManager = saveManager;
}
public void DisplayTutorial(TutorialPopupID tutorialPopupID)
{
var save = _saveManager.CurrentSave;
var tutorialVars = save?.PersistentVariables?.TutorialPopupVariables;
if (tutorialVars == null)
return;
if (tutorialVars.HasBeenDisplayed(tutorialPopupID))
return;
tutorialVars.MarkDisplayed(tutorialPopupID);
if (_settingsService.AreTutorialsEnabled())
_eventCoordinator.Publish(new DisplayTutorialPopupEvent(tutorialPopupID));
_eventCoordinator.PublishImmediate(new RequestGameSaveEvent());
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4fe74e4cef404c51a2b151b539c64295
timeCreated: 1773945075