using System.Collections.Generic; using System.Text.Json; using Godot; using Mr.BrickAdventures.scripts.Resources; using Mr.BrickAdventures.scripts.State; namespace Mr.BrickAdventures.Autoloads; /// /// Save system that serializes state to JSON using DTOs. /// public partial class SaveSystem : Node { [Export] public string SavePath { get; set; } = "user://savegame.json"; [Export] public int Version { get; set; } = 2; public static SaveSystem Instance { get; private set; } private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; public override void _Ready() { Instance = this; } public override void _ExitTree() { if (Instance == this) Instance = null; } public void SaveGame() { var store = GameStateStore.Instance; if (store == null) { GD.PrintErr("SaveSystem: GameStateStore not available."); return; } // Convert to DTO (only serializable data) var saveData = new SaveDataDto { Version = Version, Coins = store.Player.Coins, Lives = store.Player.Lives, CurrentLevel = store.Session.CurrentLevel, CompletedLevels = [.. store.Player.CompletedLevels], UnlockedLevels = new List(store.Player.UnlockedLevels), UnlockedSkillNames = GetSkillNames(store.Player.UnlockedSkills), UnlockedAchievements = new List(store.Player.UnlockedAchievements), Statistics = new Dictionary(store.Player.Statistics) }; try { var json = JsonSerializer.Serialize(saveData, JsonOptions); using var file = FileAccess.Open(SavePath, FileAccess.ModeFlags.Write); file.StoreString(json); GD.Print("Game saved to: ", SavePath); EventBus.EmitGameSaved(); } catch (System.Exception e) { GD.PrintErr($"SaveSystem: Failed to save game: {e.Message}"); } } public bool LoadGame() { if (!FileAccess.FileExists(SavePath)) { GD.Print("SaveSystem: No save file found."); return false; } try { using var file = FileAccess.Open(SavePath, FileAccess.ModeFlags.Read); var json = file.GetAsText(); var saveData = JsonSerializer.Deserialize(json, JsonOptions); if (saveData == null) { GD.PrintErr("SaveSystem: Failed to deserialize save data."); return false; } if (saveData.Version != Version) { GD.PrintErr($"SaveSystem: Version mismatch. Expected {Version}, found {saveData.Version}"); return false; } var store = GameStateStore.Instance; if (store == null) { GD.PrintErr("SaveSystem: GameStateStore not available."); return false; } // Apply loaded state store.Player.Coins = saveData.Coins; store.Player.Lives = saveData.Lives; store.Session.CurrentLevel = saveData.CurrentLevel; store.Player.CompletedLevels = saveData.CompletedLevels ?? new List(); store.Player.UnlockedLevels = saveData.UnlockedLevels ?? new List { 0 }; store.Player.UnlockedAchievements = saveData.UnlockedAchievements ?? new List(); store.Player.Statistics = saveData.Statistics ?? new Dictionary(); // Reload skills by name from SkillManager store.Player.UnlockedSkills = LoadSkillsByName(saveData.UnlockedSkillNames); GD.Print("Game loaded from: ", SavePath); return true; } catch (System.Exception e) { GD.PrintErr($"SaveSystem: Failed to load game: {e.Message}"); return false; } } private static List GetSkillNames(List skills) { var names = new List(); foreach (var skill in skills) { if (skill != null) names.Add(skill.Name); } return names; } private List LoadSkillsByName(List skillNames) { var skills = new List(); if (skillNames == null) return skills; var skillManager = GetNodeOrNull(Constants.SkillManagerPath); if (skillManager == null) { GD.PrintErr("SaveSystem: SkillManager not available to resolve skill names."); return skills; } foreach (var name in skillNames) { var skill = skillManager.GetSkillByName(name); if (skill != null) { skills.Add(skill); } else { GD.PrintErr($"SaveSystem: Skill '{name}' not found in SkillManager."); } } return skills; } public bool CheckSaveExists() => FileAccess.FileExists(SavePath); public void DeleteSave() { if (FileAccess.FileExists(SavePath)) { DirAccess.RemoveAbsolute(ProjectSettings.GlobalizePath(SavePath)); GD.Print("Save file deleted."); } } } /// /// Serializable DTO for save data - no Godot types. /// public class SaveDataDto { public int Version { get; set; } public int Coins { get; set; } public int Lives { get; set; } public int CurrentLevel { get; set; } public List CompletedLevels { get; set; } public List UnlockedLevels { get; set; } public List UnlockedSkillNames { get; set; } public List UnlockedAchievements { get; set; } public Dictionary Statistics { get; set; } }