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