Files
LowPolyRPG/Assets/Plugins/Nullframes/Intrigues/Scripts/Save System/IntrigueSaveSystem.cs

675 lines
22 KiB
C#
Raw Normal View History

2025-06-25 11:10:11 +01:00
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Nullframes.Intrigues.Utils;
using Nullframes.Intrigues.Graph;
using UnityEngine;
namespace Nullframes.Intrigues.SaveSystem {
[DefaultExecutionOrder(-350)]
[AddComponentMenu("Intrigues/Save System")]
public class IntrigueSaveSystem : MonoBehaviour {
/// <summary>
/// Indicates whether to pretty print the JSON data for better readability.
/// </summary>
public bool PrettyPrint;
/// <summary>
/// If true, the save file will be encrypted.
/// </summary>
public bool Encrypt;
/// <summary>
/// The password used to encrypt the save file.
/// </summary>
public string encryptionPassword = "My Password";
/// <summary>
/// Returns the directory where the save files are stored.
/// </summary>
private static string Path => $"{Application.persistentDataPath}/";
/// <summary>
/// Generates and returns a name for the save file using a unique ID with a .null extension.
/// </summary>
private static string FileNameExtension => "null";
/// <summary>
/// Retrieves the most recently saved file based on its last write time.
/// </summary>
public SaveFileInfo GET_LATEST_SAVE {
get {
var fileInfo = new DirectoryInfo(Path).GetFiles(@$"*.{FileNameExtension}")
.OrderByDescending(f => f.LastWriteTime).FirstOrDefault();
return fileInfo == null
? null
: new SaveFileInfo(System.IO.Path.GetFileNameWithoutExtension(fileInfo.Name), fileInfo);
}
}
/// <summary>
/// Retrieves all save files sorted by their last write time in descending order.
/// </summary>
public IEnumerable<SaveFileInfo> GET_SAVE_FILES {
get {
return new DirectoryInfo(Path).GetFiles(@$"*.{FileNameExtension}")
.OrderByDescending(f => f.LastWriteTime)
.Select(f => new SaveFileInfo(System.IO.Path.GetFileNameWithoutExtension(f.Name), f)).ToList();
}
}
/// <summary>
/// Returns a FileInfo object for a given save file name.
/// </summary>
/// <param name="saveFileName">Name of the save file.</param>
public FileInfo SaveFileInfo(string saveFileName) => new DirectoryInfo(Path)
.GetFiles(@$"*.{FileNameExtension}")
.FirstOrDefault(f => System.IO.Path.GetFileNameWithoutExtension(f.Name) == saveFileName);
/// <summary>
/// Checks if a save file with the given name exists.
/// </summary>
/// <param name="saveFileName">Name of the save file to check.</param>
public bool SaveFileExists(string saveFileName) => SaveFileInfo(saveFileName) != null;
private SaveData loadedData;
private string currentSaveFile;
public static IntrigueSaveSystem Instance;
/// <summary>
/// Checks if any save file exists.
/// </summary>
public bool AnySaveFileExists => GET_LATEST_SAVE != null;
/// <summary>
/// Provides access to the loaded save data.
/// </summary>
public SaveData Data => loadedData;
/// <summary>
/// Returns the name of the currently active save file.
/// </summary>
public string ActiveSaveFile => currentSaveFile;
/// <summary>
/// Event triggered when all Runtime Actors have been loaded.
/// </summary>
public static event Action onRuntimeActorsLoaded;
/// <summary>
/// Event triggered when data is saved.
/// </summary>
public static event Action onSave;
/// <summary>
/// Event triggered when data is loaded.
/// </summary>
public static event Action onLoad;
private void Awake() {
if (Instance == null) {
Instance = this;
DontDestroyOnLoad(this);
return;
}
Destroy(this);
}
/// <summary>
/// Clears the loaded save data. Typically used when starting a new game or similar scenarios.
/// </summary>
public void NewGame() {
loadedData = null;
IM.Load(null);
}
/// <summary>
/// Saves the game data. If a file name is provided, it will use that; otherwise, it might use a default naming mechanism.
/// </summary>
/// <param name="fileName">Optional name for the save file.</param>
public string Save(string fileName) {
if (string.IsNullOrEmpty(fileName)) {
fileName = null;
}
loadedData = new SaveData();
//Save Actor Data
foreach (var actor in IM.ActorDictionary.Values) {
var actorData = new ActorData();
actorData.Init(actor);
loadedData.actorData.Add(actorData);
}
//Save Game Data
var gameData = new GameData();
gameData.Init();
loadedData.gameData = gameData;
var jsonString = JsonUtility.ToJson(loadedData, PrettyPrint);
var fName = fileName ?? NullUtils.GenerateID();
File.WriteAllText(Path + fName + "." + FileNameExtension,
Encrypt ? Encryption.Encrypt(jsonString, encryptionPassword) : jsonString);
currentSaveFile = fName;
NDebug.Log($"Save successful. Save name: '{currentSaveFile}'");
onSave?.Invoke();
return fName;
}
/// <summary>
/// Saves the game data. If a file name is provided, it will use that; otherwise, it might use a default naming mechanism.
/// </summary>
public void Save() => Save(null);
/// <summary>
/// Overwrites the currently active save file. If there's no active save file, it creates a new save.
/// </summary>
public void SaveOverwrite() {
if (string.IsNullOrEmpty(currentSaveFile)) {
Save();
return;
}
NDebug.Log($"The save file named '{currentSaveFile}' was overwritten.");
Save(currentSaveFile);
}
/// <summary>
/// Loads the game data from the specified save file.
/// </summary>
/// <param name="fileName">The name of the save file to load.</param>
public void Load(string fileName) {
if (string.IsNullOrEmpty(fileName)) return;
fileName = System.IO.Path.GetFileNameWithoutExtension(fileName);
if (!SaveFileExists(fileName)) return;
string content = File.ReadAllText(Path + fileName + "." + FileNameExtension);
loadedData = JsonUtility.FromJson<SaveData>(Encryption.Decrypt(content, encryptionPassword));
if (loadedData == null) return;
IM.Load(loadedData);
currentSaveFile = fileName;
IRuntimeLoader.Load();
foreach (var actor in IM.ActorDictionary.Values) {
var actorSaveData = loadedData.actorData.Find(a => a.id == actor.ID);
if (actorSaveData == null) continue;
actor.LoadActor(actorSaveData);
}
NDebug.Log($"The save named '{currentSaveFile}' was successfully loaded.");
onLoad?.Invoke();
}
/// <summary>
/// Loads the runtime actors.
/// </summary>
public void LoadRuntimeActors() {
if (loadedData == null) return;
//Create Runtime Actors
foreach (var runtimeActor in loadedData.actorData) // Where(a => a.classType == 1)
{
if (IM.Actors.Any(a => a.ID == runtimeActor.id)) continue;
RuntimeActor.LoadRuntimeActor(runtimeActor);
}
onRuntimeActorsLoaded?.Invoke();
NDebug.Log("RuntimeActors were successfully loaded.");
}
/// <summary>
/// Loads the most recent save based on its last write time.
/// </summary>
public void LoadLatest() {
Load(GET_LATEST_SAVE.Name);
}
public void Delete(string saveFileName) {
if (SaveFileExists(saveFileName)) {
File.Delete(Path + saveFileName + "." + FileNameExtension);
NDebug.Log($"The save file named '{saveFileName}' was deleted.");
}
}
public void DeleteCurrent() {
if (SaveFileExists(currentSaveFile)) {
File.Delete(Path + currentSaveFile + "." + FileNameExtension);
NDebug.Log($"The save file named '{currentSaveFile}' was deleted.");
}
}
}
[Serializable]
public class SaveData {
public List<ActorData> actorData = new();
public GameData gameData;
}
[Serializable]
public class CultureData {
public string id;
public string name;
public Sprite icon;
public string description;
public List<string> femaleNameList;
public List<string> maleNameList;
public void Init(Culture culture) {
id = culture.ID;
name = culture.CultureName;
icon = culture.Icon;
description = culture.DescriptionWithoutLocalisation;
femaleNameList = new List<string>(culture.FemaleNames);
maleNameList = new List<string>(culture.MaleNames);
}
}
[Serializable]
public class FamilyData {
public string id;
public string name;
public Sprite icon;
public string description;
public string cultureId;
public List<string> policies;
public List<NVarData> variableData;
public void Init(Family family) {
id = family.ID;
name = family.FamilyName;
icon = family.Icon;
description = family.DescriptionWithoutLocalisation;
cultureId = family.Culture?.ID;
policies = new List<string>();
variableData = new List<NVarData>();
foreach (var variable in family.Variables) {
var varData = new NVarData();
varData.Init(variable);
variableData.Add(varData);
}
foreach (var policy in family.Policies) {
policies.Add(policy.ID);
}
}
}
[Serializable]
public class ClanData {
public string id;
public string name;
public Sprite icon;
public string description;
public string cultureId;
public List<string> policies;
public List<NVarData> variableData;
public void Init(Clan clan) {
id = clan.ID;
name = clan.ClanName;
icon = clan.Icon;
description = clan.DescriptionWithoutLocalisation;
cultureId = clan.Culture?.ID;
policies = new List<string>();
variableData = new List<NVarData>();
foreach ( var variable in clan.Variables ) {
var varData = new NVarData();
varData.Init(variable);
variableData.Add(varData);
}
foreach (var policy in clan.Policies) {
policies.Add(policy.ID);
}
}
}
[Serializable]
public class PolicyData {
public string id;
public string name;
public string description;
public PolicyType type;
public Sprite icon;
public void Init(Policy policy) {
id = policy.ID;
name = policy.PolicyName;
description = policy.DescriptionWithoutLocalisation;
type = policy.Type;
icon = policy.Icon;
}
}
[Serializable]
public class RoleData {
public string id;
public string name;
public string description;
public string titleForFemale;
public string titleForMale;
public string heirFilterId;
public bool inheritance;
public int capacity;
public int priority;
public Sprite icon;
public void Init(Role role) {
id = role.ID;
name = role.RoleName;
description = role.DescriptionWithoutLocalisation;
titleForFemale = role.TitleForFemale;
titleForMale = role.TitleForMale;
heirFilterId = role.FilterID;
capacity = role.Capacity;
inheritance = role.Inheritance;
priority = role.Priority;
icon = role.Icon;
}
}
[Serializable]
public class GameData {
public List<NVarData> variableData;
public List<CultureData> cultureData;
public List<PolicyData> policyData;
public List<FamilyData> familyData;
public List<ClanData> clanData;
public List<RoleData> roleData;
public void Init() {
variableData = new List<NVarData>();
cultureData = new List<CultureData>();
policyData = new List<PolicyData>();
roleData = new List<RoleData>();
familyData = new List<FamilyData>();
clanData = new List<ClanData>();
foreach (var variable in IM.Variables) {
var data = new NVarData();
data.Init(variable);
variableData.Add(data);
}
foreach (var culture in IM.Cultures) {
var data = new CultureData();
data.Init(culture);
cultureData.Add(data);
}
foreach (var policy in IM.Policies) {
var data = new PolicyData();
data.Init(policy);
policyData.Add(data);
}
foreach (var role in IM.Roles) {
var data = new RoleData();
data.Init(role);
roleData.Add(data);
}
foreach (var family in IM.Families) {
var data = new FamilyData();
data.Init(family);
familyData.Add(data);
}
foreach (var clan in IM.Clans) {
var data = new ClanData();
data.Init(clan);
clanData.Add(data);
}
}
}
[Serializable]
public class ActorData {
public string id;
public int classType;
public bool isPlayer;
public bool inheritor;
public string name;
public int age;
public string culture;
public string clan;
public string role;
public Actor.IGender gender;
public Actor.IState state;
public List<string> parents;
public List<string> children;
public List<string> spouses;
public string family;
public string familyOrigin;
public string overrideHeir;
public Sprite portrait;
public List<NVarData> variableData;
public List<SchemeData> schemeData;
public List<RelationVariableData> relationVariableData;
public void Init(Actor actor) {
id = actor.ID;
name = actor.Name;
age = actor.Age;
gender = actor.Gender;
inheritor = actor.Inheritor;
culture = actor.Culture?.ID;
clan = actor.Clan?.ID;
role = actor.Role?.ID;
state = actor.State;
overrideHeir = actor.HeirOverridden ? actor.Heir.ID : null;
portrait = actor.Portrait;
familyOrigin = actor.Origin?.ID;
family = actor.Family?.ID;
isPlayer = actor.IsPlayer;
classType = actor switch {
InitialActor => 0,
RuntimeActor => 1,
_ => classType
};
parents = new List<string>();
children = new List<string>();
spouses = new List<string>();
variableData = new List<NVarData>();
relationVariableData = new List<RelationVariableData>();
schemeData = new List<SchemeData>();
foreach (var parent in actor.Parents()) {
parents.Add(parent.ID);
}
foreach (var child in actor.Children()) {
children.Add(child.ID);
}
foreach (var spouse in actor.Spouses()) {
spouses.Add(spouse.ID);
}
foreach (var variable in actor.Variables) {
var varData = new NVarData();
varData.Init(variable);
variableData.Add(varData);
}
foreach (var specificVariable in actor.RelationVariables) {
var specificData = new RelationVariableData() {
actorId = specificVariable.Key.ID,
varData = new List<NVarData>()
};
foreach (var variable in specificVariable.Value) {
var varData = new NVarData();
varData.Init(variable);
specificData.varData.Add(varData);
}
relationVariableData.Add(specificData);
}
foreach (var scheme in actor.Schemes) {
if (scheme.Schemer == null) continue;
var _schemeData = new SchemeData();
_schemeData.Init(scheme);
this.schemeData.Add(_schemeData);
}
}
}
[Serializable]
public class RelationVariableData {
public string actorId;
public List<NVarData> varData;
}
[Serializable]
public class NVarData {
public string id;
public string name;
public NType type;
public string stringValue;
public int integerValue;
public bool boolValue;
public float floatValue;
public string actorId;
public UnityEngine.Object objectValue;
public int enumIndex;
public void Init(NVar variable) {
id = variable.id;
name = variable.name;
type = variable.Type;
switch (variable) {
case NString nString:
stringValue = nString.Value;
break;
case NInt nInt:
integerValue = nInt.Value;
break;
case NBool nBool:
boolValue = nBool.Value;
break;
case NFloat nFloat:
floatValue = nFloat.Value;
break;
case NObject nObject:
objectValue = nObject.Value;
break;
case NEnum nEnum:
enumIndex = nEnum.Index;
break;
case NActor nActor:
actorId = nActor.ActorID;
break;
}
}
}
[Serializable]
public class SchemeData {
public string id;
public string targetId;
public bool isPaused;
public string objective;
public bool isEnded;
public List<NVarData> variables;
public List<NodeInfoData> activeNodes;
public void Init(Scheme scheme) {
id = scheme.ID;
targetId = scheme.Schemer.Target != null ? scheme.Schemer.Target.ID : null;
objective = scheme.CurrentObjective;
if (scheme.Schemer != null) {
isPaused = scheme.Schemer.IsPaused;
isEnded = scheme.Schemer.IsEnded;
}
activeNodes = new List<NodeInfoData>();
foreach (var nodeInfo in scheme.Schemer.ActiveNodeList) {
var nInfo = new NodeInfoData();
nInfo.Init(nodeInfo);
activeNodes.Add(nInfo);
}
variables = new List<NVarData>();
foreach (var _nVar in scheme.Schemer.Variables) {
var nVar = new NVarData();
nVar.Init(_nVar);
nVar.id = _nVar.name;
variables.Add(nVar);
}
}
}
[Serializable]
public class NodeInfoData {
public string id;
public string nodeId;
public bool bgWorker;
public string inputName;
public string actorId;
public string actorId1;
public string actorId2;
public string clanId;
public string familyId;
public float time;
public int index;
public string sequencerId;
public string repeaterId;
public string validatorId;
public int repeatCount;
public List<string> delays;
public List<int> indexes;
public void Init(NodeInfo nodeInfo) {
id = nodeInfo.id;
nodeId = nodeInfo.node.ID;
bgWorker = nodeInfo.bgWorker;
actorId = nodeInfo.actor != null ? nodeInfo.actor.ID : null;
actorId1 = nodeInfo.dualActor.Item1 != null ? nodeInfo.dualActor.Item1.ID : null;
actorId2 = nodeInfo.dualActor.Item2 != null ? nodeInfo.dualActor.Item2.ID : null;
clanId = nodeInfo.clan?.ID;
familyId = nodeInfo.family?.ID;
inputName = nodeInfo.inputName;
time = nodeInfo.time;
index = nodeInfo.index;
sequencerId = nodeInfo.sequencer?.id;
repeaterId = nodeInfo.repeater?.id;
validatorId = nodeInfo.validator?.id;
repeatCount = nodeInfo.repeatCount;
delays = new List<string>(nodeInfo.delays);
indexes = new List<int>(nodeInfo.indexes);
}
}
public class SaveFileInfo {
public string Name { get; }
public FileInfo FileInfo { get; }
public SaveFileInfo(string name, FileInfo fileInfo) {
Name = name;
FileInfo = fileInfo;
}
}
}