156 lines
4.9 KiB
C#
156 lines
4.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// Manages player profiles backed by Easy Save files.
|
|
/// Stores a small index file at: persistentDataPath/profiles/_index.es3
|
|
/// Each profile gets its own folder: profiles/{profileId}/slot_{n}.es3
|
|
/// </summary>
|
|
public class ES3ProfileManager : MonoBehaviour
|
|
{
|
|
public static ES3ProfileManager Instance { get; private set; }
|
|
|
|
[Serializable]
|
|
public class ProfileInfo
|
|
{
|
|
public string id; // stable slug (folder name)
|
|
public string displayName; // shown in UI
|
|
public string createdAt; // yyyy-MM-dd HH:mm:ss
|
|
public string lastPlayedAt; // yyyy-MM-dd HH:mm:ss
|
|
}
|
|
|
|
private const string INDEX_FILE = "profiles/_index.es3";
|
|
private const string K_PROFILES = "profiles";
|
|
private const string K_ACTIVE = "active";
|
|
|
|
[SerializeField] private List<ProfileInfo> profiles = new();
|
|
[SerializeField] private string activeProfileId = null;
|
|
|
|
public IReadOnlyList<ProfileInfo> Profiles => profiles;
|
|
public string ActiveProfileId => activeProfileId;
|
|
|
|
private void Awake()
|
|
{
|
|
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
|
|
Instance = this;
|
|
DontDestroyOnLoad(gameObject);
|
|
|
|
LoadIndex();
|
|
if (string.IsNullOrEmpty(activeProfileId))
|
|
{
|
|
// ensure at least one profile exists
|
|
var p = CreateProfile("Player");
|
|
SetActiveProfile(p.id);
|
|
}
|
|
}
|
|
|
|
// ---------- Public API ----------
|
|
public ProfileInfo CreateProfile(string displayName)
|
|
{
|
|
var id = MakeSlug(displayName);
|
|
if (profiles.Exists(p => p.id == id))
|
|
{
|
|
// ensure uniqueness by appending a number
|
|
int n = 2;
|
|
var baseId = id;
|
|
while (profiles.Exists(p => p.id == id)) id = $"{baseId}{n++}";
|
|
}
|
|
|
|
var now = Now();
|
|
var info = new ProfileInfo { id = id, displayName = displayName, createdAt = now, lastPlayedAt = now };
|
|
profiles.Add(info);
|
|
|
|
Directory.CreateDirectory(GetProfileDirAbsolute(id));
|
|
SaveIndex();
|
|
return info;
|
|
}
|
|
|
|
public void DeleteProfile(string id)
|
|
{
|
|
var idx = profiles.FindIndex(p => p.id == id);
|
|
if (idx < 0) return;
|
|
|
|
// delete files on disk
|
|
var dir = GetProfileDirAbsolute(id);
|
|
if (Directory.Exists(dir)) Directory.Delete(dir, true);
|
|
|
|
profiles.RemoveAt(idx);
|
|
if (activeProfileId == id) activeProfileId = profiles.Count > 0 ? profiles[0].id : null;
|
|
SaveIndex();
|
|
}
|
|
|
|
public void RenameProfile(string id, string newDisplayName)
|
|
{
|
|
var p = profiles.Find(x => x.id == id);
|
|
if (p == null) return;
|
|
p.displayName = newDisplayName;
|
|
SaveIndex();
|
|
}
|
|
|
|
public void SetActiveProfile(string id)
|
|
{
|
|
if (!profiles.Exists(p => p.id == id)) return;
|
|
activeProfileId = id;
|
|
SaveIndex();
|
|
}
|
|
|
|
public ProfileInfo GetActiveProfile() => profiles.Find(p => p.id == activeProfileId);
|
|
|
|
public void TouchActiveProfileLastPlayed()
|
|
{
|
|
var p = GetActiveProfile();
|
|
if (p == null) return;
|
|
p.lastPlayedAt = Now();
|
|
SaveIndex();
|
|
}
|
|
|
|
// File/Path helpers
|
|
public string GetProfileDirRelative(string id) => $"profiles/{id}";
|
|
public string GetProfileDirAbsolute(string id) => Path.Combine(Application.persistentDataPath, "profiles", id);
|
|
public string GetSlotPathRelative(int slot) => $"{GetProfileDirRelative(activeProfileId)}/slot_{slot}.es3";
|
|
public string GetIndexPathAbsolute() => Path.Combine(Application.persistentDataPath, INDEX_FILE);
|
|
|
|
// ---------- Persistence of the index ----------
|
|
private void LoadIndex()
|
|
{
|
|
var abs = GetIndexPathAbsolute();
|
|
if (!File.Exists(abs))
|
|
{
|
|
profiles = new List<ProfileInfo>();
|
|
activeProfileId = null;
|
|
return;
|
|
}
|
|
|
|
var rel = INDEX_FILE; // ES3 expects relative to persistentDataPath
|
|
profiles = ES3.KeyExists(K_PROFILES, rel)
|
|
? ES3.Load<List<ProfileInfo>>(K_PROFILES, rel)
|
|
: new List<ProfileInfo>();
|
|
|
|
activeProfileId = ES3.KeyExists(K_ACTIVE, rel)
|
|
? ES3.Load<string>(K_ACTIVE, rel)
|
|
: null;
|
|
}
|
|
|
|
private void SaveIndex()
|
|
{
|
|
var rel = INDEX_FILE;
|
|
Directory.CreateDirectory(Path.Combine(Application.persistentDataPath, "profiles"));
|
|
ES3.Save(K_PROFILES, profiles, rel);
|
|
ES3.Save(K_ACTIVE, activeProfileId, rel);
|
|
}
|
|
|
|
// ---------- Utils ----------
|
|
private static string MakeSlug(string s)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(s)) s = "Player";
|
|
s = s.Trim();
|
|
foreach (var c in Path.GetInvalidFileNameChars())
|
|
s = s.Replace(c, '_');
|
|
return s.Replace(' ', '_').ToLowerInvariant();
|
|
}
|
|
|
|
private static string Now() => DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
|
}
|