refactor: SaveSystem now uses a DTO for player state serialization and PlayerDeathComponent delegates state changes to event handlers.

This commit is contained in:
2026-01-31 16:43:12 +01:00
parent 2fc988da27
commit 432a71a00c
2 changed files with 74 additions and 17 deletions

View File

@@ -1,16 +1,18 @@
using System.Collections.Generic;
using System.Text.Json; using System.Text.Json;
using Godot; using Godot;
using Mr.BrickAdventures.scripts.Resources;
using Mr.BrickAdventures.scripts.State; using Mr.BrickAdventures.scripts.State;
namespace Mr.BrickAdventures.Autoloads; namespace Mr.BrickAdventures.Autoloads;
/// <summary> /// <summary>
/// Save system that serializes POCOs directly to JSON. /// Save system that serializes state to JSON using DTOs.
/// </summary> /// </summary>
public partial class SaveSystem : Node public partial class SaveSystem : Node
{ {
[Export] public string SavePath { get; set; } = "user://savegame.json"; [Export] public string SavePath { get; set; } = "user://savegame.json";
[Export] public int Version { get; set; } = 2; // Bumped version for new format [Export] public int Version { get; set; } = 2;
private static readonly JsonSerializerOptions JsonOptions = new() private static readonly JsonSerializerOptions JsonOptions = new()
{ {
@@ -18,11 +20,6 @@ public partial class SaveSystem : Node
PropertyNamingPolicy = JsonNamingPolicy.CamelCase PropertyNamingPolicy = JsonNamingPolicy.CamelCase
}; };
public override void _Ready()
{
// No longer needs GameManager reference - works with GameStateStore directly
}
public void SaveGame() public void SaveGame()
{ {
var store = GameStateStore.Instance; var store = GameStateStore.Instance;
@@ -32,11 +29,18 @@ public partial class SaveSystem : Node
return; return;
} }
var saveData = new SaveData // Convert to DTO (only serializable data)
var saveData = new SaveDataDto
{ {
Version = Version, Version = Version,
Player = store.Player, Coins = store.Player.Coins,
CurrentLevel = store.Session.CurrentLevel Lives = store.Player.Lives,
CurrentLevel = store.Session.CurrentLevel,
CompletedLevels = [.. store.Player.CompletedLevels],
UnlockedLevels = new List<int>(store.Player.UnlockedLevels),
UnlockedSkillNames = GetSkillNames(store.Player.UnlockedSkills),
UnlockedAchievements = new List<string>(store.Player.UnlockedAchievements),
Statistics = new Dictionary<string, int>(store.Player.Statistics)
}; };
try try
@@ -65,7 +69,7 @@ public partial class SaveSystem : Node
{ {
using var file = FileAccess.Open(SavePath, FileAccess.ModeFlags.Read); using var file = FileAccess.Open(SavePath, FileAccess.ModeFlags.Read);
var json = file.GetAsText(); var json = file.GetAsText();
var saveData = JsonSerializer.Deserialize<SaveData>(json, JsonOptions); var saveData = JsonSerializer.Deserialize<SaveDataDto>(json, JsonOptions);
if (saveData == null) if (saveData == null)
{ {
@@ -87,9 +91,18 @@ public partial class SaveSystem : Node
} }
// Apply loaded state // Apply loaded state
store.Player = saveData.Player ?? new PlayerState(); store.Player.Coins = saveData.Coins;
store.Player.Lives = saveData.Lives;
store.Session.CurrentLevel = saveData.CurrentLevel; store.Session.CurrentLevel = saveData.CurrentLevel;
store.Player.CompletedLevels = saveData.CompletedLevels ?? new List<int>();
store.Player.UnlockedLevels = saveData.UnlockedLevels ?? new List<int> { 0 };
store.Player.UnlockedAchievements = saveData.UnlockedAchievements ?? new List<string>();
store.Player.Statistics = saveData.Statistics ?? new Dictionary<string, int>();
// Reload skills by name from SkillManager
store.Player.UnlockedSkills = LoadSkillsByName(saveData.UnlockedSkillNames);
GD.Print("Game loaded from: ", SavePath); GD.Print("Game loaded from: ", SavePath);
return true; return true;
} }
@@ -100,6 +113,44 @@ public partial class SaveSystem : Node
} }
} }
private static List<string> GetSkillNames(List<SkillData> skills)
{
var names = new List<string>();
foreach (var skill in skills)
{
if (skill != null)
names.Add(skill.Name);
}
return names;
}
private List<SkillData> LoadSkillsByName(List<string> skillNames)
{
var skills = new List<SkillData>();
if (skillNames == null) return skills;
var skillManager = GetNodeOrNull<SkillManager>(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 bool CheckSaveExists() => FileAccess.FileExists(SavePath);
public void DeleteSave() public void DeleteSave()
@@ -113,11 +164,17 @@ public partial class SaveSystem : Node
} }
/// <summary> /// <summary>
/// Container for save data. /// Serializable DTO for save data - no Godot types.
/// </summary> /// </summary>
public class SaveData public class SaveDataDto
{ {
public int Version { get; set; } public int Version { get; set; }
public PlayerState Player { get; set; } public int Coins { get; set; }
public int Lives { get; set; }
public int CurrentLevel { get; set; } public int CurrentLevel { get; set; }
public List<int> CompletedLevels { get; set; }
public List<int> UnlockedLevels { get; set; }
public List<string> UnlockedSkillNames { get; set; }
public List<string> UnlockedAchievements { get; set; }
public Dictionary<string, int> Statistics { get; set; }
} }

View File

@@ -32,7 +32,7 @@ public partial class PlayerDeathComponent : Node2D
effect.Scale = EffectScale; effect.Scale = EffectScale;
} }
_gameManager.RemoveLives(1); // Lives are now decremented by LivesStateHandler via PlayerDied event
_gameManager.ResetCurrentSessionState(); // Session state is reset by LivesStateHandler as well
} }
} }