using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using Revitalize.Framework.Objects; using Revitalize.Framework.Objects.Furniture; using Revitalize.Framework.Utilities.Serialization.ContractResolvers; using StardewValley; using StardewValley.Objects; namespace Revitalize.Framework.Utilities { /// /// Handles serialization of all objects in existence. /// /// TODO: Make JConvert that has same settings to implement a toJSon string obj /// public class Serializer { private JsonSerializer serializer; /// /// All files to be cleaned up after loading. /// private Dictionary> filesToDelete = new Dictionary>(); public List itemsToRemove = new List(); private JsonSerializerSettings settings; /// /// Constructor. /// public Serializer() { this.serializer = new JsonSerializer(); this.serializer.Formatting = Formatting.Indented; this.serializer.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; this.serializer.NullValueHandling = NullValueHandling.Include; this.serializer.ContractResolver = new NetFieldContract(); this.addConverter(new Framework.Utilities.Serialization.Converters.RectangleConverter()); this.addConverter(new Framework.Utilities.Serialization.Converters.Texture2DConverter()); this.addConverter(new Framework.Utilities.Serialization.Converters.ItemCoverter()); //this.addConverter(new Framework.Utilities.Serialization.Converters.CustomObjectDataConverter()); //this.addConverter(new Framework.Utilities.Serialization.Converters.NetFieldConverter()); //this.addConverter(new Framework.Utilities.Serialization.Converters.Vector2Converter()); //this.gatherAllFilesForCleanup(); this.settings = new JsonSerializerSettings(); foreach(JsonConverter converter in this.serializer.Converters) { this.settings.Converters.Add(converter); } this.settings.Formatting = Formatting.Indented; this.settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; this.settings.NullValueHandling = NullValueHandling.Include; this.settings.ContractResolver = new NetFieldContract(); } /// /// Process all the save data for objects to be deleted by this mod. /// private void gatherAllFilesForCleanup() { if (!Directory.Exists(Path.Combine(Revitalize.ModCore.ModHelper.DirectoryPath, "SaveData"))) Directory.CreateDirectory(Path.Combine(Revitalize.ModCore.ModHelper.DirectoryPath, "SaveData")); this.filesToDelete.Clear(); string[] directories = Directory.GetDirectories(Path.Combine(Revitalize.ModCore.ModHelper.DirectoryPath, "SaveData")); foreach (string playerData in directories) { string objectPath = Path.Combine(playerData, "SavedObjectInformation"); string[] objectFiles = Directory.GetFiles(objectPath); foreach (string file in objectFiles) { string playerName = new DirectoryInfo(objectPath).Parent.Name; if (this.filesToDelete.ContainsKey(playerName)){ this.filesToDelete[playerName].Add(file); //Revitalize.ModCore.log("Added File: " + file); } else { this.filesToDelete.Add(playerName, new List()); //Revitalize.ModCore.log("Added Player Key: " + playerName); this.filesToDelete[playerName].Add(file); //Revitalize.ModCore.log("Added File: " + file); } } } } private void deleteFilesBeforeSave() { if (!Directory.Exists(Path.Combine(Revitalize.ModCore.ModHelper.DirectoryPath, "SaveData"))) Directory.CreateDirectory(Path.Combine(Revitalize.ModCore.ModHelper.DirectoryPath, "SaveData")); this.filesToDelete.Clear(); string[] directories = Directory.GetDirectories(Path.Combine(Revitalize.ModCore.ModHelper.DirectoryPath, "SaveData")); foreach (string playerData in directories) { string objectPath = Path.Combine(playerData, "SavedObjectInformation"); string[] objectFiles = Directory.GetFiles(objectPath); foreach (string file in objectFiles) { string playerName = new DirectoryInfo(objectPath).Parent.Name; if (playerName != this.getUniqueCharacterString()) return; else { File.Delete(file); } } } } /// /// Called after load to deal with internal file cleanUp /// public void afterLoad() { this.deleteAllUnusedFiles(); //this.removeNullObjects(); this.restoreModObjects(); } public void restoreModObjects() { foreach (var v in ModCore.ObjectGroups) { foreach (var obj in v.Value.objects.Values) { (obj as CustomObject).replaceAfterLoad(); } } foreach (GameLocation loc in Game1.locations) { foreach (StardewValley.Object c in loc.Objects.Values) { if (c is Chest) { List toRemove = new List(); List toAdd = new List(); foreach (Item o in (c as Chest).items) { if (o == null) continue; if (o is Chest && o.Name != "Chest") { ModCore.log("Found a custom object in a chest!"); string jsonString = o.Name; string guidName = jsonString.Split(new string[] { "GUID=" }, StringSplitOptions.None)[1]; ModCore.log(jsonString); string type = jsonString.Split('|')[2]; Item I = (Item)ModCore.Serializer.DeserializeGUID(guidName, Type.GetType(type)); if (I is MultiTiledObject) { (I as MultiTiledObject).recreate(); } toAdd.Add(I); toRemove.Add(o); //Item i = Serializer.DeserializeFromJSONString(jsonString); //ModCore.log("Deserialized item is: "+i.Name); } } foreach (Item i in toRemove) { (c as Chest).items.Remove(i); } foreach (Item I in toAdd) { (c as Chest).items.Add(I); } } else if (c is StorageFurnitureTile) { foreach (Item o in (c as StorageFurnitureTile).info.inventory.items) { if (o is Chest && o.Name != "Chest") { ModCore.log("Found a custom object in a chest!"); } } } } } List toAdd2 = new List(); List toRemove2 = new List(); foreach (Item I in Game1.player.Items) { if (I == null) continue; else { if (I is Chest && I.Name != "Chest") { ModCore.log("Found a custom object in a chest!"); string jsonString = I.Name; string guidName = jsonString.Split(new string[] { "GUID=" }, StringSplitOptions.None)[1]; ModCore.log(jsonString); string type = jsonString.Split('|')[2]; Item ret = (Item)ModCore.Serializer.DeserializeGUID(guidName, Type.GetType(type)); if (ret is MultiTiledObject) { (ret as MultiTiledObject).recreate(); } toAdd2.Add(ret); toRemove2.Add(I); //Item i = Serializer.DeserializeFromJSONString(jsonString); //ModCore.log("Deserialized item is: "+i.Name); } } } foreach (Item i in toRemove2) { Game1.player.Items.Remove(i); } foreach (Item I in toAdd2) { Game1.player.addItemToInventory(I); } } public void returnToTitle() { //this.gatherAllFilesForCleanup(); } private void removeNullObjects() { List removalList = new List(); foreach(Item I in Game1.player.Items) { if (I == null) continue; if (I.DisplayName.Contains("Revitalize.Framework") && (I is Chest)) { removalList.Add(I); } } foreach(Item I in removalList) { Game1.player.Items.Remove(I); } } /// /// Removes the file from all files that will be deleted. /// /// /// private void removeFileFromDeletion(string playerDirectory, string fileName) { if (this.filesToDelete.ContainsKey(playerDirectory)) { //Revitalize.ModCore.log("Removing from deletion: " + fileName); this.filesToDelete[playerDirectory].Remove(fileName); } else { //Revitalize.ModCore.log("Found key: " + playerDirectory); //Revitalize.ModCore.log("Found file: " + fileName); } } /// /// Deletes unused object data. /// private void deleteAllUnusedFiles() { foreach(KeyValuePair> pair in this.filesToDelete) { foreach (string file in pair.Value) { File.Delete(file); } } } /// /// Adds a new converter to the json serializer. /// /// The type of json converter to add to the Serializer. public void addConverter(JsonConverter converter) { this.serializer.Converters.Add(converter); } /// /// Deserializes an object from a .json file. /// /// The type of object to deserialize into. /// The path to the file. /// An object of specified type T. public T Deserialize(string p) { string json = ""; foreach (string line in File.ReadLines(p)) { json += line; } using (StreamReader sw = new StreamReader(p)) using (JsonReader reader = new JsonTextReader(sw)) { var obj = this.serializer.Deserialize(reader); return obj; } } /// /// Deserializes an object from a .json file. /// /// The type of object to deserialize into. /// The path to the file. /// An object of specified type T. public object Deserialize(string p,Type T) { string json = ""; foreach (string line in File.ReadLines(p)) { json += line; } using (StreamReader sw = new StreamReader(p)) using (JsonReader reader = new JsonTextReader(sw)) { object obj = this.serializer.Deserialize(reader,T); return obj; } } /// /// Serializes an object to a .json file. /// /// /// public void Serialize(string path, object o) { using (StreamWriter sw = new StreamWriter(path)) using (JsonWriter writer = new JsonTextWriter(sw)) { this.serializer.Serialize(writer, o); } } /// /// Serialize a data structure into an file. /// /// /// public void SerializeGUID(string fileName,object obj) { string path = Path.Combine(Revitalize.ModCore.ModHelper.DirectoryPath, "SaveData", Game1.player.Name + "_" + Game1.player.UniqueMultiplayerID, "SavedObjectInformation", fileName + ".json"); Directory.CreateDirectory(Path.GetDirectoryName(path)); this.Serialize(path, obj); } /// /// Deserialze a file into it's proper data structure. /// /// The type of data structure to deserialze to. /// The name of the file to deserialize from. /// A data structure object deserialize from a json string in a file. public object DeserializeGUID(string fileName,Type T) { string path=Path.Combine(Revitalize.ModCore.ModHelper.DirectoryPath, "SaveData", Game1.player.Name + "_" + Game1.player.UniqueMultiplayerID, "SavedObjectInformation", fileName + ".json"); this.removeFileFromDeletion((Game1.player.Name + "_" + Game1.player.UniqueMultiplayerID), path); return this.Deserialize(path, T); } /// /// Deserialze a file into it's proper data structure. /// /// The type of data structure to deserialze to. /// The name of the file to deserialize from. /// A data structure object deserialize from a json string in a file. public T DeserializeGUID(string fileName) { string path = Path.Combine(Revitalize.ModCore.ModHelper.DirectoryPath, "SaveData", Game1.player.Name + "_" + Game1.player.UniqueMultiplayerID, "SavedObjectInformation", fileName + ".json"); //this.removeFileFromDeletion((Game1.player.Name + "_" + Game1.player.UniqueMultiplayerID),path); if (File.Exists(path)) { //ModCore.log("Deseralizing file:" + path); return this.Deserialize(path); } else { throw new Exception("Can't deserialize file. Default returned. " + path); } } /// /// Converts objects to json form. /// /// /// public string ToJSONString(object o) { return JsonConvert.SerializeObject(o, this.settings); } /// /// Converts from json form to objects. /// /// /// /// public T DeserializeFromJSONString(string info) { return JsonConvert.DeserializeObject(info,this.settings); } /// /// Converts from json form to objects. /// /// /// /// public object DeserializeFromJSONString(string info, Type T) { return JsonConvert.DeserializeObject(info, T, this.settings); } public T DeserializeContentFile(string pathToFile) { if (File.Exists(pathToFile)) { return this.Deserialize(pathToFile); } else { return default(T); } } /// /// Serializes a content file if it doesn't already exist. If it does exist this does nothing as to not override the content file. /// /// The name to name the file. So a file named MyFile would be a MyFile.json /// The actual to serialize. /// The sub folder path inside of the Content folder for this mod. public void SerializeContentFile(string fileName, object obj,string extensionFolder) { string path = Path.Combine(Revitalize.ModCore.ModHelper.DirectoryPath, "Content", extensionFolder,fileName+ ".json"); Directory.CreateDirectory(Path.GetDirectoryName(path)); if (File.Exists(path)) return; this.Serialize(path, obj); } public void DayEnding_CleanUpFilesForDeletion(object o, StardewModdingAPI.Events.DayEndingEventArgs sender) { //ModCore.log("Day ending now delete files!"); this.deleteFilesBeforeSave(); } /// /// Gets the unique character path string. /// /// public string getUniqueCharacterString() { return Game1.player.Name + "_" + Game1.player.UniqueMultiplayerID; } /// /// https://stackoverflow.com/questions/2742276/how-do-i-check-if-a-type-is-a-subtype-or-the-type-of-an-object /// /// /// /// public bool IsSameOrSubclass(Type potentialBase, Type potentialDescendant) { return potentialDescendant.IsSubclassOf(potentialBase) || potentialDescendant == potentialBase; } public bool IsSubclass(Type potentialBase, Type potentialDescendant) { return potentialDescendant.IsSubclassOf(potentialBase); } } }