Fome further save load work

This commit is contained in:
Michal Pikulski
2025-11-03 10:08:44 +01:00
parent f0897c3e4a
commit cb7889b257
11 changed files with 1817 additions and 64 deletions

View File

@@ -7,16 +7,28 @@ using UnityEngine.SceneManagement;
using AppleHills.Core.Settings;
using Bootstrap;
using Core;
using Core.SaveLoad;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using Utils;
namespace PuzzleS
{
/// <summary>
/// Save data structure for puzzle progress
/// </summary>
[Serializable]
public class PuzzleSaveData
{
public string levelId;
public List<string> completedStepIds;
public List<string> unlockedStepIds;
}
/// <summary>
/// Manages puzzle step registration, dependency management, and step completion for the puzzle system.
/// </summary>
public class PuzzleManager : MonoBehaviour
public class PuzzleManager : MonoBehaviour, ISaveParticipant
{
private static PuzzleManager _instance;
@@ -48,10 +60,23 @@ namespace PuzzleS
public event Action<PuzzleLevelDataSO> OnLevelDataLoaded;
public event Action<PuzzleLevelDataSO> OnAllPuzzlesComplete;
private HashSet<PuzzleStepSO> _completedSteps = new HashSet<PuzzleStepSO>();
private HashSet<PuzzleStepSO> _unlockedSteps = new HashSet<PuzzleStepSO>();
// Save/Load state tracking - string-based for timing independence
private HashSet<string> _completedSteps = new HashSet<string>();
private HashSet<string> _unlockedSteps = new HashSet<string>();
// Save/Load restoration tracking
private bool _isDataRestored = false;
private bool _hasBeenRestored = false;
private List<ObjectiveStepBehaviour> _pendingRegistrations = new List<ObjectiveStepBehaviour>();
// Registration for ObjectiveStepBehaviour
private Dictionary<PuzzleStepSO, ObjectiveStepBehaviour> _stepBehaviours = new Dictionary<PuzzleStepSO, ObjectiveStepBehaviour>();
/// <summary>
/// Returns true if this participant has already had its state restored.
/// Used by SaveLoadManager to prevent double-restoration.
/// </summary>
public bool HasBeenRestored => _hasBeenRestored;
void Awake()
{
@@ -70,6 +95,13 @@ namespace PuzzleS
SceneManagerService.Instance.SceneLoadCompleted += OnSceneLoadCompleted;
SceneManagerService.Instance.SceneLoadStarted += OnSceneLoadStarted;
// Register with save/load system
BootCompletionService.RegisterInitAction(() =>
{
SaveLoadManager.Instance.RegisterParticipant(this);
Logging.Debug("[PuzzleManager] Registered with SaveLoadManager");
});
// Find player transform
_playerTransform = GameObject.FindGameObjectWithTag("Player")?.transform;
@@ -96,6 +128,11 @@ namespace PuzzleS
SceneManagerService.Instance.SceneLoadStarted -= OnSceneLoadStarted;
}
// Unregister from save/load system
SaveLoadManager.Instance.UnregisterParticipant(GetSaveId());
Logging.Debug("[PuzzleManager] Unregistered from SaveLoadManager");
// Release addressable handle if needed
if (_levelDataLoadOperation.IsValid())
{
@@ -296,12 +333,20 @@ namespace PuzzleS
_stepBehaviours.Add(behaviour.stepData, behaviour);
Logging.Debug($"[Puzzles] Registered step: {behaviour.stepData.stepId} on {behaviour.gameObject.name}");
// Only update state if data is already loaded
if (_isDataLoaded && _currentLevelData != null)
// Use pending registration pattern for save/load timing independence
if (_isDataRestored)
{
// Data already restored - update immediately
UpdateStepState(behaviour);
}
// Otherwise, the state will be updated when data loads in UpdateAllRegisteredBehaviors
else
{
// Data not restored yet - add to pending queue
if (!_pendingRegistrations.Contains(behaviour))
{
_pendingRegistrations.Add(behaviour);
}
}
}
}
@@ -313,11 +358,11 @@ namespace PuzzleS
if (behaviour?.stepData == null) return;
// If step is already completed, ignore
if (_completedSteps.Contains(behaviour.stepData))
if (_completedSteps.Contains(behaviour.stepData.stepId))
return;
// If step is already unlocked, update the behaviour
if (_unlockedSteps.Contains(behaviour.stepData))
if (_unlockedSteps.Contains(behaviour.stepData.stepId))
{
behaviour.UnlockStep();
}
@@ -349,6 +394,13 @@ namespace PuzzleS
{
if (_currentLevelData == null) return;
// Don't unlock initial steps if we've restored from save
if (_isDataRestored)
{
Logging.Debug("[Puzzles] Skipping UnlockInitialSteps - data was restored from save");
return;
}
// Unlock initial steps
foreach (var step in _currentLevelData.initialSteps)
{
@@ -364,10 +416,10 @@ namespace PuzzleS
/// <param name="step">The completed step.</param>
public void MarkPuzzleStepCompleted(PuzzleStepSO step)
{
if (_completedSteps.Contains(step)) return;
if (_completedSteps.Contains(step.stepId)) return;
if (_currentLevelData == null) return;
_completedSteps.Add(step);
_completedSteps.Add(step.stepId);
Logging.Debug($"[Puzzles] Step completed: {step.stepId}");
// Broadcast completion
@@ -408,18 +460,11 @@ namespace PuzzleS
{
foreach (var depId in dependencies)
{
// Find the dependency step
bool dependencyMet = false;
foreach (var completedStep in _completedSteps)
// Check if dependency is in completed steps
if (!_completedSteps.Contains(depId))
{
if (completedStep.stepId == depId)
{
dependencyMet = true;
break;
}
return false;
}
if (!dependencyMet) return false;
}
}
@@ -432,8 +477,8 @@ namespace PuzzleS
/// <param name="step">The step to unlock.</param>
private void UnlockStep(PuzzleStepSO step)
{
if (_unlockedSteps.Contains(step)) return;
_unlockedSteps.Add(step);
if (_unlockedSteps.Contains(step.stepId)) return;
_unlockedSteps.Add(step.stepId);
if (_stepBehaviours.TryGetValue(step, out var behaviour))
{
@@ -452,7 +497,18 @@ namespace PuzzleS
{
if (_currentLevelData == null) return;
if (_currentLevelData.IsLevelComplete(_completedSteps))
// Check if all steps are completed
bool allComplete = true;
foreach (var step in _currentLevelData.allSteps)
{
if (step != null && !_completedSteps.Contains(step.stepId))
{
allComplete = false;
break;
}
}
if (allComplete)
{
Logging.Debug("[Puzzles] All puzzles complete! Level finished.");
@@ -466,7 +522,7 @@ namespace PuzzleS
/// </summary>
public bool IsStepUnlocked(PuzzleStepSO step)
{
return _unlockedSteps.Contains(step);
return step != null && _unlockedSteps.Contains(step.stepId);
}
/// <summary>
@@ -476,7 +532,7 @@ namespace PuzzleS
/// <returns>True if the step has been completed, false otherwise</returns>
public bool IsPuzzleStepCompleted(string stepId)
{
return _completedSteps.Any(step => step.stepId == stepId);
return _completedSteps.Contains(stepId); // O(1) lookup!
}
/// <summary>
@@ -495,5 +551,89 @@ namespace PuzzleS
{
return _isDataLoaded;
}
#region ISaveParticipant Implementation
/// <summary>
/// Get unique save ID for this puzzle manager instance
/// </summary>
public string GetSaveId()
{
string sceneName = SceneManager.GetActiveScene().name;
return $"{sceneName}/PuzzleManager";
}
/// <summary>
/// Serialize current puzzle state to JSON
/// </summary>
public string SerializeState()
{
if (_currentLevelData == null)
{
Logging.Warning("[PuzzleManager] Cannot serialize state - no level data loaded");
return "{}";
}
var saveData = new PuzzleSaveData
{
levelId = _currentLevelData.levelId,
completedStepIds = _completedSteps.ToList(),
unlockedStepIds = _unlockedSteps.ToList()
};
string json = JsonUtility.ToJson(saveData);
Logging.Debug($"[PuzzleManager] Serialized puzzle state: {_completedSteps.Count} completed, {_unlockedSteps.Count} unlocked");
return json;
}
/// <summary>
/// Restore puzzle state from serialized JSON data
/// </summary>
public void RestoreState(string data)
{
if (string.IsNullOrEmpty(data) || data == "{}")
{
Logging.Debug("[PuzzleManager] No puzzle save data to restore");
_isDataRestored = true;
_hasBeenRestored = true;
return;
}
try
{
var saveData = JsonUtility.FromJson<PuzzleSaveData>(data);
if (saveData == null)
{
Logging.Warning("[PuzzleManager] Failed to deserialize puzzle save data");
_isDataRestored = true;
_hasBeenRestored = true;
return;
}
// Restore step IDs directly - no timing dependency on level data!
_completedSteps = new HashSet<string>(saveData.completedStepIds ?? new List<string>());
_unlockedSteps = new HashSet<string>(saveData.unlockedStepIds ?? new List<string>());
_isDataRestored = true;
_hasBeenRestored = true;
Logging.Debug($"[PuzzleManager] Restored puzzle state: {_completedSteps.Count} completed, {_unlockedSteps.Count} unlocked steps");
// Update any behaviors that registered before RestoreState was called
foreach (var behaviour in _pendingRegistrations)
{
UpdateStepState(behaviour);
}
_pendingRegistrations.Clear();
}
catch (System.Exception e)
{
Debug.LogError($"[PuzzleManager] Error restoring puzzle state: {e.Message}");
_isDataRestored = true;
_hasBeenRestored = true;
}
}
#endregion
}
}