From 717deee0cd7c64a081c9bf322f47fdc27bf6d2f3 Mon Sep 17 00:00:00 2001 From: Michal Pikulski Date: Wed, 5 Nov 2025 20:17:32 +0100 Subject: [PATCH] Fix issues with puzzle loading order and add saving state when using Menu --- .../Backgrounds/LegendaryBackground.png.meta | 26 ++++ .../Cards/Frames/NormalFrame.png.meta | 26 ++++ .../Scripts/Core/SaveLoad/SaveLoadManager.cs | 120 ++++++++++++++++-- Assets/Scripts/Levels/LevelSwitchMenu.cs | 4 +- Assets/Scripts/Levels/MinigameSwitch.cs | 14 +- .../Scripts/PuzzleS/ObjectiveStepBehaviour.cs | 4 +- Assets/Scripts/PuzzleS/PuzzleManager.cs | 22 +++- Assets/Settings/Developer/DebugSettings.asset | 4 +- 8 files changed, 201 insertions(+), 19 deletions(-) diff --git a/Assets/Art/Textures/Cards/Backgrounds/LegendaryBackground.png.meta b/Assets/Art/Textures/Cards/Backgrounds/LegendaryBackground.png.meta index e77a96dd..6b47396a 100644 --- a/Assets/Art/Textures/Cards/Backgrounds/LegendaryBackground.png.meta +++ b/Assets/Art/Textures/Cards/Backgrounds/LegendaryBackground.png.meta @@ -122,6 +122,32 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 sprites: diff --git a/Assets/Art/Textures/Cards/Frames/NormalFrame.png.meta b/Assets/Art/Textures/Cards/Frames/NormalFrame.png.meta index d903d689..a5e1fabb 100644 --- a/Assets/Art/Textures/Cards/Frames/NormalFrame.png.meta +++ b/Assets/Art/Textures/Cards/Frames/NormalFrame.png.meta @@ -122,6 +122,32 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 sprites: diff --git a/Assets/Scripts/Core/SaveLoad/SaveLoadManager.cs b/Assets/Scripts/Core/SaveLoad/SaveLoadManager.cs index 090d9e57..a5c4da76 100644 --- a/Assets/Scripts/Core/SaveLoad/SaveLoadManager.cs +++ b/Assets/Scripts/Core/SaveLoad/SaveLoadManager.cs @@ -180,6 +180,67 @@ namespace Core.SaveLoad return participant; } + #endregion + + #region Unlocked Minigames Management + + /// + /// Marks a minigame as unlocked in the global save data. + /// This is separate from scene-specific participant states and persists across all saves. + /// + /// The name/identifier of the minigame (typically scene name) + public void UnlockMinigame(string minigameName) + { + if (string.IsNullOrEmpty(minigameName)) + { + Logging.Warning("[SaveLoadManager] Attempted to unlock minigame with null or empty name"); + return; + } + + if (currentSaveData == null) + { + Logging.Warning("[SaveLoadManager] Cannot unlock minigame - no save data loaded"); + return; + } + + if (currentSaveData.unlockedMinigames == null) + { + currentSaveData.unlockedMinigames = new System.Collections.Generic.List(); + } + + if (!currentSaveData.unlockedMinigames.Contains(minigameName)) + { + currentSaveData.unlockedMinigames.Add(minigameName); + Logging.Debug($"[SaveLoadManager] Unlocked minigame: {minigameName}"); + } + } + + /// + /// Checks if a minigame has been unlocked. + /// + /// The name/identifier of the minigame + /// True if the minigame is unlocked, false otherwise + public bool IsMinigameUnlocked(string minigameName) + { + if (string.IsNullOrEmpty(minigameName)) + return false; + + if (currentSaveData == null || currentSaveData.unlockedMinigames == null) + return false; + + return currentSaveData.unlockedMinigames.Contains(minigameName); + } + + /// + /// Gets a read-only list of all unlocked minigames. + /// + public System.Collections.Generic.IReadOnlyList GetUnlockedMinigames() + { + if (currentSaveData == null || currentSaveData.unlockedMinigames == null) + return new System.Collections.Generic.List(); + + return currentSaveData.unlockedMinigames.AsReadOnly(); + } #endregion @@ -343,29 +404,68 @@ namespace Core.SaveLoad Logging.Debug("[SaveLoadManager] Saving scene-specific data..."); - // Collect scene data from LifecycleManager + // Build a dictionary of all data to save + var allSceneData = new Dictionary(); + + // Collect scene data from ManagedBehaviours via LifecycleManager if (Lifecycle.LifecycleManager.Instance != null) { var sceneData = Lifecycle.LifecycleManager.Instance.BroadcastSceneSaveRequested(); - - // Remove old scene data and add new - if (currentSaveData.participantStates != null) + foreach (var kvp in sceneData) { - // Remove existing entries for these SaveIds (to avoid duplicates) - currentSaveData.participantStates.RemoveAll(entry => sceneData.ContainsKey(entry.saveId)); - - // Add new scene data - foreach (var kvp in sceneData) + allSceneData[kvp.Key] = kvp.Value; + } + Logging.Debug($"[SaveLoadManager] Collected {sceneData.Count} ManagedBehaviour scene states"); + } + + // Collect data from ISaveParticipants (all currently registered, identified by SaveId) + foreach (var kvp in participants.ToList()) + { + string saveId = kvp.Key; + ISaveParticipant participant = kvp.Value; + + try + { + string serializedState = participant.SerializeState(); + allSceneData[saveId] = serializedState; + Logging.Debug($"[SaveLoadManager] Captured state for ISaveParticipant: {saveId}"); + } + catch (Exception ex) + { + Logging.Warning($"[SaveLoadManager] Exception while serializing ISaveParticipant '{saveId}': {ex}"); + } + } + + // Update existing entries or add new ones (matches SaveAsync() pattern) + if (currentSaveData.participantStates != null) + { + int updatedCount = 0; + + foreach (var kvp in allSceneData) + { + var existingEntry = currentSaveData.participantStates.Find(e => e.saveId == kvp.Key); + if (existingEntry != null) { + // Update existing entry in place + existingEntry.serializedState = kvp.Value; + } + else + { + // Add new entry currentSaveData.participantStates.Add(new ParticipantStateEntry { saveId = kvp.Key, serializedState = kvp.Value }); } + updatedCount++; } - Logging.Debug($"[SaveLoadManager] Updated {sceneData.Count} scene data entries in memory"); + Logging.Debug($"[SaveLoadManager] Updated {updatedCount} scene data entries in memory"); + } + else + { + Logging.Warning("[SaveLoadManager] participantStates list is null, cannot save scene data"); } } diff --git a/Assets/Scripts/Levels/LevelSwitchMenu.cs b/Assets/Scripts/Levels/LevelSwitchMenu.cs index e5cb5f8d..f9d58005 100644 --- a/Assets/Scripts/Levels/LevelSwitchMenu.cs +++ b/Assets/Scripts/Levels/LevelSwitchMenu.cs @@ -255,9 +255,9 @@ namespace Levels if (_switchData == null) return; - var data = SaveLoadManager.Instance?.currentSaveData; + // Use the new public API to check unlock status string minigameName = _switchData.targetMinigameSceneName; - bool unlocked = data?.unlockedMinigames != null && !string.IsNullOrEmpty(minigameName) && data.unlockedMinigames.Contains(minigameName); + bool unlocked = SaveLoadManager.Instance != null && SaveLoadManager.Instance.IsMinigameUnlocked(minigameName); // Show/hide padlock if (padlockImage) padlockImage.gameObject.SetActive(!unlocked); diff --git a/Assets/Scripts/Levels/MinigameSwitch.cs b/Assets/Scripts/Levels/MinigameSwitch.cs index e4430c1a..05c5ab1b 100644 --- a/Assets/Scripts/Levels/MinigameSwitch.cs +++ b/Assets/Scripts/Levels/MinigameSwitch.cs @@ -102,7 +102,13 @@ namespace Levels isUnlocked = true; gameObject.SetActive(true); - // Save will happen automatically on next save cycle via ISaveParticipant + // Add to global unlocked minigames list + if (switchData != null && !string.IsNullOrEmpty(switchData.targetLevelSceneName)) + { + Core.SaveLoad.SaveLoadManager.Instance?.UnlockMinigame(switchData.targetLevelSceneName); + } + + // Save will happen automatically on next save cycle via SaveableInteractable } #if UNITY_EDITOR @@ -231,6 +237,12 @@ namespace Levels isUnlocked = data.isUnlocked; + // Sync with global unlocked minigames list + if (isUnlocked && switchData != null && !string.IsNullOrEmpty(switchData.targetLevelSceneName)) + { + Core.SaveLoad.SaveLoadManager.Instance?.UnlockMinigame(switchData.targetLevelSceneName); + } + // Show/hide based on unlock state gameObject.SetActive(isUnlocked); } diff --git a/Assets/Scripts/PuzzleS/ObjectiveStepBehaviour.cs b/Assets/Scripts/PuzzleS/ObjectiveStepBehaviour.cs index f148f6b9..76b5839a 100644 --- a/Assets/Scripts/PuzzleS/ObjectiveStepBehaviour.cs +++ b/Assets/Scripts/PuzzleS/ObjectiveStepBehaviour.cs @@ -34,8 +34,6 @@ namespace PuzzleS protected override void Awake() { - base.Awake(); - _interactable = GetComponent(); // Initialize the indicator if it exists, but ensure it's hidden initially @@ -58,6 +56,8 @@ namespace PuzzleS Logging.Warning($"[Puzzles] Indicator prefab for {stepData?.stepId} does not implement IPuzzlePrompt"); } } + + base.Awake(); } protected override void OnManagedAwake() diff --git a/Assets/Scripts/PuzzleS/PuzzleManager.cs b/Assets/Scripts/PuzzleS/PuzzleManager.cs index 206dc56a..da51e85e 100644 --- a/Assets/Scripts/PuzzleS/PuzzleManager.cs +++ b/Assets/Scripts/PuzzleS/PuzzleManager.cs @@ -50,7 +50,24 @@ namespace PuzzleS // Save system configuration public override bool AutoRegisterForSave => true; - public override string SaveId => $"{SceneManager.GetActiveScene().name}/PuzzleManager"; + + /// + /// SaveId uses CurrentGameplayScene instead of GetActiveScene() because PuzzleManager + /// lives in DontDestroyOnLoad and needs to save/load data per-scene. + /// + public override string SaveId + { + get + { + string sceneName = SceneManagerService.Instance?.CurrentGameplayScene; + if (string.IsNullOrEmpty(sceneName)) + { + // Fallback during early initialization + sceneName = SceneManager.GetActiveScene().name; + } + return $"{sceneName}/PuzzleManager"; + } + } /// /// Singleton instance of the PuzzleManager. @@ -602,7 +619,8 @@ namespace PuzzleS // Update any behaviors that registered before RestoreState was called foreach (var behaviour in _pendingRegistrations) { - UpdateStepState(behaviour); + if(behaviour != null) + UpdateStepState(behaviour); } _pendingRegistrations.Clear(); } diff --git a/Assets/Settings/Developer/DebugSettings.asset b/Assets/Settings/Developer/DebugSettings.asset index 84efebe7..59dda546 100644 --- a/Assets/Settings/Developer/DebugSettings.asset +++ b/Assets/Settings/Developer/DebugSettings.asset @@ -16,8 +16,8 @@ MonoBehaviour: pauseTimeOnPauseGame: 0 useSaveLoadSystem: 1 bootstrapLogVerbosity: 0 - settingsLogVerbosity: 1 - gameManagerLogVerbosity: 1 + settingsLogVerbosity: 0 + gameManagerLogVerbosity: 0 sceneLogVerbosity: 0 saveLoadLogVerbosity: 0 inputLogVerbosity: 0