using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using PyTK.CustomElementHandler; using Revitalize.Framework.Objects; using Revitalize.Framework.Objects.Furniture; using Revitalize.Framework.Utilities.Serialization.ContractResolvers; using Revitalize.Framework.Utilities.Serialization.Converters; using StardewValley; using StardewValley.Objects; namespace Revitalize.Framework.Utilities { /// /// Handles serialization of all objects in existence. /// public class Serializer { /// /// The actual json serializer. /// private JsonSerializer serializer; /// /// All files to be cleaned up after loading. /// private Dictionary> filesToDelete = new Dictionary>(); /// /// The items to remove for deletion. /// private List itemsToRemove = new List(); /// /// The settings used by the seralizer /// private JsonSerializerSettings settings; public static NetFieldConverter NetFieldConverter; /// /// 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(); NetFieldConverter = new NetFieldConverter(); 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(NetFieldConverter); //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() { ModCore.log("WHAT"); this.deleteAllUnusedFiles(); //this.removeNullObjects(); this.restoreModObjects(); } /// /// Restore mod objects to inventories and world after load. /// public void restoreModObjects() { //ModCore.log("Restore all mod objects!"); //Replace all items in the world. List objsToRestore = new List(); foreach (var v in ModCore.ObjectGroups) { foreach (var obj in v.Value.objects.Values) { //(obj as CustomObject).replaceAfterLoad(); objsToRestore.Add(obj as CustomObject); } } foreach(CustomObject o in objsToRestore) { (o as CustomObject).replaceAfterLoad(); } //Replace all held items or items in inventories. foreach (GameLocation loc in LocationUtilities.GetAllLocations()) { //ModCore.log("Looking at location: " + loc); 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") { Item I = this.GetItemFromChestName(o.Name); ModCore.log("Found a custom item in a chest!"); toAdd.Add(I); toRemove.Add(o); } } 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 Chest && c.Name != "Chest") { loc.objects[c.TileLocation] = (StardewValley.Object)this.GetItemFromChestName(c.Name); ModCore.log("Found a custom item that is a chest!"); } else if (c is CustomObject) { if ((c as CustomObject).info.inventory == null) continue; List toRemove = new List(); List toAdd = new List(); foreach (Item o in (c as CustomObject).info.inventory.items) { if (o == null) continue; if (o is Chest && o.Name != "Chest") { Item I = this.GetItemFromChestName(o.Name); toAdd.Add(I); toRemove.Add(o); } } foreach (Item i in toRemove) { (c as Chest).items.Remove(i); } foreach (Item I in toAdd) { (c as Chest).items.Add(I); } if (c.heldObject.Value != null) { if (c.heldObject.Value is Chest && c.heldObject.Value.Name != "Chest") { ModCore.log("Found a custom object as a held object!"); Item I = this.GetItemFromChestName(c.heldObject.Value.Name); c.heldObject.Value = (StardewValley.Object)I; } } } else if (c is StardewValley.Object) { if (c.heldObject.Value != null) { if (c.heldObject.Value is Chest && c.heldObject.Value.Name != "Chest") { ModCore.log("Found a custom object as a held object!"); Item I = this.GetItemFromChestName(c.heldObject.Value.Name); c.heldObject.Value = (StardewValley.Object)I; } } } } } 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") { Item ret = this.GetItemFromChestName(I.Name); toAdd2.Add(ret); toRemove2.Add(I); } } } foreach (Item i in toRemove2) { Game1.player.Items.Remove(i); } foreach (Item I in toAdd2) { Game1.player.addItemToInventory(I); } } /// /// Gets an Item recreated from PYTK's chest replacement objects. /// /// /// public Item GetItemFromChestName(string JsonName) { //ModCore.log("Found a custom object in a chest!"); string jsonString = JsonName; //ModCore.log(JsonName); string dataSplit= jsonString.Split(new string[] { "<" }, StringSplitOptions.None)[1]; string backUpGUID = dataSplit.Split('|')[0]; string[] guidArr = jsonString.Split(new string[] { "|" }, StringSplitOptions.None); foreach(string s in guidArr) { //ModCore.log(s); } string guidName = guidArr[guidArr.Length - 1]; guidName = guidName.Substring(5); try { Guid g = Guid.Parse(guidName); } catch (Exception err) { Guid d = Guid.Parse(backUpGUID); guidName = backUpGUID; } //ModCore.log("THE GUID IS:"+ guidName); //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(); } return I; } public Item DeserializeFromFarmhandInventory(string JsonName) { //ModCore.log("Found a custom object in a chest!"); string jsonString = JsonName; //ModCore.log(JsonName); string dataSplit = jsonString.Split(new string[] { "<" }, StringSplitOptions.None)[2]; string backUpGUID = dataSplit.Split('|')[0]; string[] guidArr = jsonString.Split(new string[] { "|" }, StringSplitOptions.None); string infoStr = jsonString.Split(new string[] { "<" }, StringSplitOptions.None)[0]; string guidStr= jsonString.Split(new string[] { "<" }, StringSplitOptions.None)[1]; CustomObjectData pyTkData = ModCore.Serializer.DeserializeFromJSONString(dataSplit); Type t = Type.GetType(pyTkData.type); string id = pyTkData.id; //Need Item info string guidName = backUpGUID; string infoSplit = infoStr.Split('|')[3]; infoSplit = infoSplit.Substring(3); BasicItemInformation info = ModCore.Serializer.DeserializeFromJSONString(infoSplit); CustomObject clone = (CustomObject)ModCore.ObjectManager.getItemByIDAndType(id, t); if (clone != null) { clone.info = info; ModCore.log("Guid is????:"+guidStr); clone.guid = Guid.Parse(guidStr); return clone; } return null; } public void returnToTitle() { //this.gatherAllFilesForCleanup(); } [Obsolete] 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); } /// /// Deserailizes a content file for the mod. /// /// /// /// 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); } /// /// Deletes all .json saved objects before saving. /// /// /// 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); } } }