MPV of save/load system
This commit is contained in:
@@ -26,3 +26,4 @@ MonoBehaviour:
|
|||||||
- {fileID: 3528960956969533010, guid: 53eea3840d3cde34a9768b8773a3a7e8, type: 3}
|
- {fileID: 3528960956969533010, guid: 53eea3840d3cde34a9768b8773a3a7e8, type: 3}
|
||||||
- {fileID: 6895404274863911569, guid: 840f3d8a936b39a41b5896328a692005, type: 3}
|
- {fileID: 6895404274863911569, guid: 840f3d8a936b39a41b5896328a692005, type: 3}
|
||||||
- {fileID: 3863019143023165617, guid: 774e30e3f0b1d0d49bad0c2abf11038a, type: 3}
|
- {fileID: 3863019143023165617, guid: 774e30e3f0b1d0d49bad0c2abf11038a, type: 3}
|
||||||
|
- {fileID: 5034240524438268576, guid: b15ba9d3d508ef244b0eeb76404dc9de, type: 3}
|
||||||
|
|||||||
63
Assets/Editor/Tools/ClearSavesMenuItem.cs
Normal file
63
Assets/Editor/Tools/ClearSavesMenuItem.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
using System.IO;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Editor.Tools
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Editor utility to clear all save data from the SaveLoadManager's save folder
|
||||||
|
/// </summary>
|
||||||
|
public static class ClearSavesMenuItem
|
||||||
|
{
|
||||||
|
[MenuItem("AppleHills/Clear Saves")]
|
||||||
|
private static void ClearSaves()
|
||||||
|
{
|
||||||
|
// Construct the save folder path (matches SaveLoadManager.DefaultSaveFolder)
|
||||||
|
string saveFolder = Path.Combine(Application.persistentDataPath, "GameSaves");
|
||||||
|
|
||||||
|
if (!Directory.Exists(saveFolder))
|
||||||
|
{
|
||||||
|
Debug.Log($"[ClearSaves] Save folder does not exist: {saveFolder}");
|
||||||
|
EditorUtility.DisplayDialog("Clear Saves", "No save data found to clear.", "OK");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm with the user
|
||||||
|
bool confirmed = EditorUtility.DisplayDialog(
|
||||||
|
"Clear Saves",
|
||||||
|
$"Are you sure you want to delete all save data?\n\nFolder: {saveFolder}",
|
||||||
|
"Delete All",
|
||||||
|
"Cancel"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!confirmed)
|
||||||
|
{
|
||||||
|
Debug.Log("[ClearSaves] User cancelled save deletion");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Delete all files in the save folder
|
||||||
|
string[] files = Directory.GetFiles(saveFolder);
|
||||||
|
int deletedCount = 0;
|
||||||
|
|
||||||
|
foreach (string file in files)
|
||||||
|
{
|
||||||
|
File.Delete(file);
|
||||||
|
deletedCount++;
|
||||||
|
Debug.Log($"[ClearSaves] Deleted: {file}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log($"[ClearSaves] Successfully deleted {deletedCount} save file(s)");
|
||||||
|
EditorUtility.DisplayDialog("Clear Saves", $"Successfully deleted {deletedCount} save file(s).", "OK");
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError($"[ClearSaves] Failed to clear saves: {ex}");
|
||||||
|
EditorUtility.DisplayDialog("Clear Saves", $"Error clearing saves:\n{ex.Message}", "OK");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
3
Assets/Editor/Tools/ClearSavesMenuItem.cs.meta
Normal file
3
Assets/Editor/Tools/ClearSavesMenuItem.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 747ab9e66b014771bc1486f9ab073f90
|
||||||
|
timeCreated: 1761569785
|
||||||
48
Assets/Prefabs/Managers/SaveLoadManager.prefab
Normal file
48
Assets/Prefabs/Managers/SaveLoadManager.prefab
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!1 &5034240524438268576
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 536416456044738252}
|
||||||
|
- component: {fileID: 6631817730171642502}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: SaveLoadManager
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!4 &536416456044738252
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 5034240524438268576}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!114 &6631817730171642502
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 5034240524438268576}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: a1731f4790be4ec3bab71506427768d7, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: AppleHillsScripts::Core.SaveLoad.SaveLoadManager
|
||||||
|
currentSaveData:
|
||||||
|
playedDivingTutorial: 0
|
||||||
7
Assets/Prefabs/Managers/SaveLoadManager.prefab.meta
Normal file
7
Assets/Prefabs/Managers/SaveLoadManager.prefab.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b15ba9d3d508ef244b0eeb76404dc9de
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
File diff suppressed because one or more lines are too long
3
Assets/Scripts/Core/SaveLoad.meta
Normal file
3
Assets/Scripts/Core/SaveLoad.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0541e0e1c3dd41f7a61773e4bc2c64ed
|
||||||
|
timeCreated: 1761553949
|
||||||
29
Assets/Scripts/Core/SaveLoad/SaveLoadData.cs
Normal file
29
Assets/Scripts/Core/SaveLoad/SaveLoadData.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Core.SaveLoad
|
||||||
|
{
|
||||||
|
[System.Serializable]
|
||||||
|
public class SaveLoadData
|
||||||
|
{
|
||||||
|
public bool playedDivingTutorial = false;
|
||||||
|
|
||||||
|
// Snapshot of the player's card collection (MVP)
|
||||||
|
public CardCollectionState cardCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimal DTOs for card persistence
|
||||||
|
[System.Serializable]
|
||||||
|
public class CardCollectionState
|
||||||
|
{
|
||||||
|
public int boosterPackCount;
|
||||||
|
public List<SavedCardEntry> cards = new List<SavedCardEntry>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[System.Serializable]
|
||||||
|
public class SavedCardEntry
|
||||||
|
{
|
||||||
|
public string definitionId;
|
||||||
|
public AppleHills.Data.CardSystem.CardRarity rarity;
|
||||||
|
public int copiesOwned;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Assets/Scripts/Core/SaveLoad/SaveLoadData.cs.meta
Normal file
3
Assets/Scripts/Core/SaveLoad/SaveLoadData.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1704f1fc7e6b4a398f39fb86cec265f8
|
||||||
|
timeCreated: 1761553972
|
||||||
238
Assets/Scripts/Core/SaveLoad/SaveLoadManager.cs
Normal file
238
Assets/Scripts/Core/SaveLoad/SaveLoadManager.cs
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.IO;
|
||||||
|
using Bootstrap;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Core.SaveLoad
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Simple Save/Load manager that follows the project's bootstrap pattern.
|
||||||
|
/// - Singleton instance
|
||||||
|
/// - Registers a post-boot init action with BootCompletionService
|
||||||
|
/// - Exposes simple async Save/Load methods (PlayerPrefs-backed placeholder)
|
||||||
|
/// - Fires events on completion
|
||||||
|
/// This is intended as boilerplate to be expanded with a real serialization backend.
|
||||||
|
/// </summary>
|
||||||
|
public class SaveLoadManager : MonoBehaviour
|
||||||
|
{
|
||||||
|
private static SaveLoadManager _instance;
|
||||||
|
public static SaveLoadManager Instance => _instance;
|
||||||
|
|
||||||
|
// Path
|
||||||
|
private static string DefaultSaveFolder => Path.Combine(Application.persistentDataPath, "GameSaves");
|
||||||
|
public SaveLoadData currentSaveData;
|
||||||
|
|
||||||
|
// State
|
||||||
|
public bool IsSaving { get; private set; }
|
||||||
|
public bool IsLoading { get; private set; }
|
||||||
|
public bool IsSaveDataLoaded { get; private set; }
|
||||||
|
|
||||||
|
// Events
|
||||||
|
public event Action<string> OnSaveCompleted;
|
||||||
|
public event Action<string> OnLoadCompleted;
|
||||||
|
|
||||||
|
void Awake()
|
||||||
|
{
|
||||||
|
_instance = this;
|
||||||
|
IsSaveDataLoaded = false;
|
||||||
|
BootCompletionService.RegisterInitAction(InitializePostBoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnApplicationQuit()
|
||||||
|
{
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializePostBoot()
|
||||||
|
{
|
||||||
|
Logging.Debug("[SaveLoadManager] Post-boot initialization complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDestroy()
|
||||||
|
{
|
||||||
|
if (_instance == this)
|
||||||
|
_instance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetFilePath(string slot)
|
||||||
|
{
|
||||||
|
return Path.Combine(DefaultSaveFolder, $"save_{slot}.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Entry point to save to a named slot. Starts an async coroutine that writes to PlayerPrefs
|
||||||
|
/// (placeholder behavior). Fires OnSaveCompleted when finished.
|
||||||
|
/// </summary>
|
||||||
|
public void Save(string slot = "default")
|
||||||
|
{
|
||||||
|
if (IsSaving)
|
||||||
|
{
|
||||||
|
Logging.Warning("[SaveLoadManager] Save called while another save is in progress");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StartCoroutine(SaveAsync(slot));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Entry point to load from a named slot. Starts an async coroutine that reads from PlayerPrefs
|
||||||
|
/// (placeholder behavior). Fires OnLoadCompleted when finished.
|
||||||
|
/// </summary>
|
||||||
|
public void Load(string slot = "default")
|
||||||
|
{
|
||||||
|
if (IsLoading)
|
||||||
|
{
|
||||||
|
Logging.Warning("[SaveLoadManager] Load called while another load is in progress");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StartCoroutine(LoadAsync(slot));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This is stupid overkill, but over verbose logging is king for now
|
||||||
|
private IEnumerator SaveAsync(string slot)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[SaveLoadManager] Starting save for slot '{slot}'");
|
||||||
|
IsSaving = true;
|
||||||
|
|
||||||
|
string path = GetFilePath(slot);
|
||||||
|
string tempPath = path + ".tmp";
|
||||||
|
string json = null;
|
||||||
|
bool prepFailed = false;
|
||||||
|
|
||||||
|
// Prep phase: ensure folder exists and serialize (no yields allowed inside try/catch)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(DefaultSaveFolder);
|
||||||
|
|
||||||
|
if (currentSaveData == null)
|
||||||
|
{
|
||||||
|
Logging.Debug("[SaveLoadManager] currentSaveData is null, creating default instance before saving");
|
||||||
|
currentSaveData = new SaveLoadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull latest card collection snapshot from CardSystem before serializing (don't overwrite with null)
|
||||||
|
if (Data.CardSystem.CardSystemManager.Instance != null)
|
||||||
|
{
|
||||||
|
currentSaveData.cardCollection = Data.CardSystem.CardSystemManager.Instance.ExportCardCollectionState();
|
||||||
|
}
|
||||||
|
|
||||||
|
json = JsonUtility.ToJson(currentSaveData, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Warning($"[SaveLoadManager] Exception during save prep for slot '{slot}': {ex}");
|
||||||
|
prepFailed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prepFailed || string.IsNullOrEmpty(json))
|
||||||
|
{
|
||||||
|
// Ensure we clean up state and notify listeners outside of the try/catch
|
||||||
|
IsSaving = false;
|
||||||
|
OnSaveCompleted?.Invoke(slot);
|
||||||
|
Logging.Warning($"[SaveLoadManager] Aborting save for slot '{slot}' due to prep failure");
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write phase: perform IO and atomic move with fallback
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.WriteAllText(tempPath, json);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
File.Delete(path);
|
||||||
|
File.Move(tempPath, path);
|
||||||
|
}
|
||||||
|
catch (Exception moveEx)
|
||||||
|
{
|
||||||
|
Logging.Warning($"[SaveLoadManager] Atomic move failed for '{path}', attempting overwrite: {moveEx}");
|
||||||
|
File.Copy(tempPath, path, true);
|
||||||
|
File.Delete(tempPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.Debug($"[SaveLoadManager] Save data written to '{path}'");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Warning($"[SaveLoadManager] Exception while saving slot '{slot}': {ex}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsSaving = false;
|
||||||
|
OnSaveCompleted?.Invoke(slot);
|
||||||
|
Debug.Log($"[SaveLoadManager] Save completed for slot '{slot}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This is stupid overkill, but over verbose logging is king for now
|
||||||
|
private IEnumerator LoadAsync(string slot)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[SaveLoadManager] Starting load for slot '{slot}'");
|
||||||
|
IsLoading = true;
|
||||||
|
string path = GetFilePath(slot);
|
||||||
|
|
||||||
|
// Fast-path: no save file
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
Logging.Debug($"[SaveLoadManager] No save found at '{path}', creating defaults");
|
||||||
|
currentSaveData = new SaveLoadData();
|
||||||
|
|
||||||
|
// Simulate async operation and finish
|
||||||
|
yield return null;
|
||||||
|
|
||||||
|
IsLoading = false;
|
||||||
|
IsSaveDataLoaded = true;
|
||||||
|
OnLoadCompleted?.Invoke(slot);
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate async operation (optional)
|
||||||
|
yield return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string json = File.ReadAllText(path);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(json))
|
||||||
|
{
|
||||||
|
Logging.Warning($"[SaveLoadManager] Save file at '{path}' is empty. Creating defaults.");
|
||||||
|
currentSaveData = new SaveLoadData();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Attempt to deserialize; if it fails or returns null, fall back to defaults
|
||||||
|
var loaded = JsonUtility.FromJson<SaveLoadData>(json);
|
||||||
|
if (loaded == null)
|
||||||
|
{
|
||||||
|
Logging.Warning($"[SaveLoadManager] Deserialized save data was null for slot '{slot}'. Creating defaults.");
|
||||||
|
currentSaveData = new SaveLoadData();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentSaveData = loaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Warning($"[SaveLoadManager] Exception while reading/deserializing save file at '{path}': {ex}");
|
||||||
|
currentSaveData = new SaveLoadData();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsLoading = false;
|
||||||
|
IsSaveDataLoaded = true;
|
||||||
|
OnLoadCompleted?.Invoke(slot);
|
||||||
|
Logging.Debug($"[SaveLoadManager] Load completed for slot '{slot}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Assets/Scripts/Core/SaveLoad/SaveLoadManager.cs.meta
Normal file
3
Assets/Scripts/Core/SaveLoad/SaveLoadManager.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a1731f4790be4ec3bab71506427768d7
|
||||||
|
timeCreated: 1761553854
|
||||||
@@ -4,6 +4,7 @@ using System.Linq;
|
|||||||
using AppleHills.Data.CardSystem;
|
using AppleHills.Data.CardSystem;
|
||||||
using Bootstrap;
|
using Bootstrap;
|
||||||
using Core;
|
using Core;
|
||||||
|
using Core.SaveLoad;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
@@ -36,6 +37,9 @@ namespace Data.CardSystem
|
|||||||
public event Action<CardData> OnCardRarityUpgraded;
|
public event Action<CardData> OnCardRarityUpgraded;
|
||||||
public event Action<int> OnBoosterCountChanged;
|
public event Action<int> OnBoosterCountChanged;
|
||||||
|
|
||||||
|
// Keep a reference to unsubscribe safely
|
||||||
|
private Action<string> _onSaveDataLoadedHandler;
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
_instance = this;
|
_instance = this;
|
||||||
@@ -49,9 +53,6 @@ namespace Data.CardSystem
|
|||||||
// Load card definitions from Addressables
|
// Load card definitions from Addressables
|
||||||
LoadCardDefinitionsFromAddressables();
|
LoadCardDefinitionsFromAddressables();
|
||||||
|
|
||||||
// Build lookup dictionary
|
|
||||||
BuildDefinitionLookup();
|
|
||||||
|
|
||||||
Logging.Debug("[CardSystemManager] Post-boot initialization complete");
|
Logging.Debug("[CardSystemManager] Post-boot initialization complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,9 +88,54 @@ namespace Data.CardSystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build lookup now that cards are loaded
|
||||||
|
BuildDefinitionLookup();
|
||||||
|
|
||||||
|
// Apply saved state when save data is available
|
||||||
|
if (SaveLoadManager.Instance != null)
|
||||||
|
{
|
||||||
|
if (SaveLoadManager.Instance.IsSaveDataLoaded)
|
||||||
|
{
|
||||||
|
ApplySavedCardStateIfAvailable();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SaveLoadManager.Instance.OnLoadCompleted += OnSaveDataLoadedHandler;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
if (SaveLoadManager.Instance != null)
|
||||||
|
{
|
||||||
|
SaveLoadManager.Instance.OnLoadCompleted -= OnSaveDataLoadedHandler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply saved state if present in the SaveLoadManager
|
||||||
|
private void ApplySavedCardStateIfAvailable()
|
||||||
|
{
|
||||||
|
var data = SaveLoadManager.Instance?.currentSaveData;
|
||||||
|
if (data?.cardCollection != null)
|
||||||
|
{
|
||||||
|
ApplyCardCollectionState(data.cardCollection);
|
||||||
|
Logging.Debug("[CardSystemManager] Applied saved card collection state after loading definitions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event handler for when save data load completes
|
||||||
|
private void OnSaveDataLoadedHandler(string slot)
|
||||||
|
{
|
||||||
|
ApplySavedCardStateIfAvailable();
|
||||||
|
if (SaveLoadManager.Instance != null)
|
||||||
|
{
|
||||||
|
SaveLoadManager.Instance.OnLoadCompleted -= OnSaveDataLoadedHandler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Builds a lookup dictionary for quick access to card definitions by ID
|
/// Builds a lookup dictionary for quick access to card definitions by ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -383,5 +429,58 @@ namespace Data.CardSystem
|
|||||||
|
|
||||||
return (float)collectedOfRarity / totalOfRarity * 100f;
|
return (float)collectedOfRarity / totalOfRarity * 100f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Export current card collection to a serializable snapshot
|
||||||
|
/// </summary>
|
||||||
|
public CardCollectionState ExportCardCollectionState()
|
||||||
|
{
|
||||||
|
var state = new CardCollectionState
|
||||||
|
{
|
||||||
|
boosterPackCount = playerInventory.BoosterPackCount,
|
||||||
|
cards = new List<SavedCardEntry>()
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var card in playerInventory.CollectedCards.Values)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(card.DefinitionId)) continue;
|
||||||
|
state.cards.Add(new SavedCardEntry
|
||||||
|
{
|
||||||
|
definitionId = card.DefinitionId,
|
||||||
|
rarity = card.Rarity,
|
||||||
|
copiesOwned = card.CopiesOwned
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Apply a previously saved snapshot to the runtime inventory
|
||||||
|
/// </summary>
|
||||||
|
public void ApplyCardCollectionState(CardCollectionState state)
|
||||||
|
{
|
||||||
|
if (state == null) return;
|
||||||
|
|
||||||
|
playerInventory.ClearAllCards();
|
||||||
|
playerInventory.BoosterPackCount = state.boosterPackCount;
|
||||||
|
OnBoosterCountChanged?.Invoke(playerInventory.BoosterPackCount);
|
||||||
|
|
||||||
|
foreach (var entry in state.cards)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(entry.definitionId)) continue;
|
||||||
|
if (_definitionLookup.TryGetValue(entry.definitionId, out var def))
|
||||||
|
{
|
||||||
|
// Create from definition to ensure links, then overwrite runtime fields
|
||||||
|
var cd = def.CreateCardData();
|
||||||
|
cd.Rarity = entry.rarity;
|
||||||
|
cd.CopiesOwned = entry.copiesOwned;
|
||||||
|
playerInventory.AddCard(cd);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logging.Warning($"[CardSystemManager] Saved card definition not found: {entry.definitionId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ namespace AppleHills.Tests
|
|||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
[CustomEditor(typeof(CardSystemTester))]
|
[CustomEditor(typeof(CardSystemTester))]
|
||||||
public class CardSystemTesterEditor : Editor
|
public class CardSystemTesterEditor : UnityEditor.Editor
|
||||||
{
|
{
|
||||||
public override void OnInspectorGUI()
|
public override void OnInspectorGUI()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ namespace UI.CardSystem
|
|||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
[CustomEditor(typeof(CardUIElement))]
|
[CustomEditor(typeof(CardUIElement))]
|
||||||
public class CardUIElementEditor : Editor
|
public class CardUIElementEditor : UnityEditor.Editor
|
||||||
{
|
{
|
||||||
public override void OnInspectorGUI()
|
public override void OnInspectorGUI()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using Bootstrap;
|
using Bootstrap;
|
||||||
using Core;
|
using Core;
|
||||||
|
using Core.SaveLoad;
|
||||||
using Input;
|
using Input;
|
||||||
using Pixelplacement;
|
using Pixelplacement;
|
||||||
using UI.Core;
|
using UI.Core;
|
||||||
@@ -31,8 +32,10 @@ namespace UI.Tutorial
|
|||||||
|
|
||||||
void InitializeTutorial()
|
void InitializeTutorial()
|
||||||
{
|
{
|
||||||
if (playTutorial)
|
if (playTutorial && !SaveLoadManager.Instance.currentSaveData.playedDivingTutorial)
|
||||||
{
|
{
|
||||||
|
// TODO: Possibly do it better, but for now just mark tutorial as played immediately
|
||||||
|
SaveLoadManager.Instance.currentSaveData.playedDivingTutorial = true;
|
||||||
// pause the game, hide UI, and register for input overrides
|
// pause the game, hide UI, and register for input overrides
|
||||||
GameManager.Instance.RequestPause(this);
|
GameManager.Instance.RequestPause(this);
|
||||||
UIPageController.Instance.HideAllUI();
|
UIPageController.Instance.HideAllUI();
|
||||||
|
|||||||
Reference in New Issue
Block a user