using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
///
/// 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
///
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 profiles = new();
[SerializeField] private string activeProfileId = null;
public IReadOnlyList 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();
activeProfileId = null;
return;
}
var rel = INDEX_FILE; // ES3 expects relative to persistentDataPath
profiles = ES3.KeyExists(K_PROFILES, rel)
? ES3.Load>(K_PROFILES, rel)
: new List();
activeProfileId = ES3.KeyExists(K_ACTIVE, rel)
? ES3.Load(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");
}