using System; using System.IO; using System.Linq; using Microsoft.Xna.Framework; using StardewModdingAPI; using StardewValley; using StardewValley.Characters; using StardewValley.Menus; using StardewValley.Monsters; using SFarmer = StardewValley.Farmer; namespace Omegasis.SaveAnywhere { /// Provides methods for saving and loading game data. internal class SaveManager { /********* ** Properties *********/ /// The player for which to save data. private readonly SFarmer Player; /// Simplifies access to game code. private readonly IReflectionHelper Reflection; /// Writes messages to the console and log file. private readonly IMonitor Monitor; /// A callback invoked when villagers are reset during a load. private readonly Action OnVillagersReset; /// The full path to the folder in which to store data for this player. private readonly string SavePath; /// The full path to the folder in which to store animal data for this player. private readonly string SaveAnimalsPath; /// The full path to the folder in which to store villager data for this player. private readonly string SaveVillagersPath; /// Whether we should save at the next opportunity. private bool WaitingToSave; /********* ** Public methods *********/ /// Construct an instance. /// The player for which to save data. /// The full path to the mod folder. /// Writes messages to the console and log file. /// Simplifies access to game code. /// A callback invoked when villagers are reset during a load. public SaveManager(SFarmer player, string modPath, IMonitor monitor, IReflectionHelper reflection, Action onVillagersReset) { // save info this.Player = player; this.Monitor = monitor; this.Reflection = reflection; this.OnVillagersReset = onVillagersReset; // generate paths this.SavePath = Path.Combine(modPath, "Save_Data", player.name); this.SaveAnimalsPath = Path.Combine(this.SavePath, "Animals"); this.SaveVillagersPath = Path.Combine(this.SavePath, "NPC_Save_Info"); } /// Perform any required update logic. public void Update() { // perform passive save if (this.WaitingToSave && Game1.activeClickableMenu == null) { Game1.activeClickableMenu = new SaveGameMenu(); this.WaitingToSave = false; } } /// Save all game data. public void SaveGameAndPositions() { // save game data Farm farm = Game1.getFarm(); if (farm.shippingBin.Any()) { Game1.activeClickableMenu = new NewShippingMenu(farm.shippingBin, this.Reflection); farm.shippingBin.Clear(); farm.lastItemShipped = null; this.WaitingToSave = true; } else Game1.activeClickableMenu = new SaveGameMenu(); // save custom data Directory.CreateDirectory(this.SaveAnimalsPath); Directory.CreateDirectory(this.SaveVillagersPath); this.SavePlayerPosition(); this.SaveHorsePosition(); this.SavePetPosition(); this.SaveVillagerPositions(); } /// Load all game data. public void LoadPositions() { if (!this.HasSaveData()) return; this.LoadPlayerPosition(); this.LoadHorsePosition(); this.LoadPetPosition(); bool anyVillagersMoved = this.LoadVillagerPositions(); if (anyVillagersMoved) this.OnVillagersReset?.Invoke(); } /********* ** Private methods *********/ /// Save the horse state to the save file. private void SaveHorsePosition() { // find horse Horse horse = Utility.findHorse(); if (horse == null) return; // get horse info string map = horse.currentLocation.name; Point tile = horse.getTileLocationPoint(); // save data string path = Path.Combine(this.SaveAnimalsPath, $"Horse_Save_Info_{this.Player.name}.txt"); string[] text = new string[20]; text[0] = "Horse: Save_Anywhere Info. Editing this might break some things."; text[1] = "===================================================================================="; text[2] = "Horse Current Map Name"; text[3] = map; text[4] = "Horse X Position"; text[5] = tile.X.ToString(); text[6] = "Horse Y Position"; text[7] = tile.Y.ToString(); File.WriteAllLines(path, text); } /// Reset the horse to the saved state. private void LoadHorsePosition() { // find horse Horse horse = Utility.findHorse(); if (horse == null) return; // get file path string path = Path.Combine(this.SaveAnimalsPath, $"Horse_Save_Info_{this.Player.name}.txt"); if (!File.Exists(path)) return; // read saved data string[] text = File.ReadAllLines(path); string map = Convert.ToString(text[3]); int x = Convert.ToInt32(text[5]); int y = Convert.ToInt32(text[7]); // update horse Game1.warpCharacter(horse, map, new Point(x, y), false, true); } /// Save the villager states to the save file. private void SaveVillagerPositions() { foreach (NPC npc in Utility.getAllCharacters()) { // ignore non-villagers if (npc is Pet || npc is Monster) continue; // get NPC data string name = npc.name; string map = npc.currentLocation.name; Point tile = npc.getTileLocationPoint(); // save data string path = Path.Combine(this.SaveVillagersPath, npc.name + ".txt"); string[] text = new string[20]; text[0] = "NPC: Save_Anywhere Info. Editing this might break some things."; text[1] = "===================================================================================="; text[2] = "NPC Name"; text[3] = name; text[4] = "NPC Current Map Name"; text[5] = map; text[6] = "NPC X Position"; text[7] = tile.X.ToString(); text[8] = "NPC Y Position"; text[9] = tile.Y.ToString(); File.WriteAllLines(path, text); } } /// Reset the villagers to their saved state. /// Returns whether any villagers changed position. private bool LoadVillagerPositions() { bool anyLoaded = false; foreach (NPC npc in Utility.getAllCharacters()) { // ignore non-villagers if (npc is Pet || npc is Monster) continue; // get file path string path = Path.Combine(this.SaveVillagersPath, npc.name + ".txt"); if (!File.Exists(path)) { this.Monitor.Log($"No save data for {npc.name} villager, skipping.", LogLevel.Error); continue; } // read data string[] text = File.ReadAllLines(path); string map = Convert.ToString(text[5]); int x = Convert.ToInt32(text[7]); int y = Convert.ToInt32(text[9]); if (string.IsNullOrEmpty(map)) continue; // update NPC anyLoaded = true; Game1.warpCharacter(npc, map, new Point(x, y), false, true); } return anyLoaded; } /// Save the pet state to the save file. private void SavePetPosition() { if (!this.Player.hasPet()) return; // find pet Pet pet = Utility.getAllCharacters().OfType().FirstOrDefault(); if (pet == null) return; // get pet info string map = pet.currentLocation.name; Point tile = pet.getTileLocationPoint(); // save data string path = Path.Combine(this.SaveAnimalsPath, $"Pet_Save_Info_{this.Player.name}.txt"); string[] text = new string[20]; text[0] = "Pet: Save_Anywhere Info. Editing this might break some things."; text[1] = "===================================================================================="; text[2] = "Pet Current Map Name"; text[3] = map; text[4] = "Pet X Position"; text[5] = tile.X.ToString(); text[6] = "Pet Y Position"; text[7] = tile.Y.ToString(); File.WriteAllLines(path, text); } /// Reset the pet to the saved state. private void LoadPetPosition() { if (!this.Player.hasPet()) return; // find pet Pet pet = Utility.getAllCharacters().OfType().FirstOrDefault(); if (pet == null) return; // get file path string path = Path.Combine(this.SaveAnimalsPath, $"Pet_Save_Info_{this.Player.name}.txt"); if (!File.Exists(path)) return; // read saved data string[] text = File.ReadAllLines(path); string map = Convert.ToString(text[3]); int x = Convert.ToInt32(text[5]); int y = Convert.ToInt32(text[7]); // update pet Game1.warpCharacter(pet, map, new Point(x, y), false, true); } /// Save the player state to the save file. private void SavePlayerPosition() { // get player info string map = this.Player.currentLocation.name; Point tile = this.Player.getTileLocationPoint(); // save data string path = Path.Combine(this.SavePath, $"Player_Save_Info_{this.Player.name}.txt"); string[] text = new string[20]; text[0] = "Player: Save_Anywhere Info. Editing this might break some things."; text[1] = "===================================================================================="; text[2] = "Player Current Game Time"; text[3] = Game1.timeOfDay.ToString(); text[4] = "Player Current Map Name"; text[5] = map; text[6] = "Player X Position"; text[7] = tile.X.ToString(); text[8] = "Player Y Position"; text[9] = tile.Y.ToString(); File.WriteAllLines(path, text); } /// Reset the player to the saved state. private void LoadPlayerPosition() { // get file path string path = Path.Combine(this.SavePath, $"Player_Save_Info_{this.Player.name}.txt"); if (!File.Exists(path)) return; // read saved data string[] text = File.ReadAllLines(path); int time = Convert.ToInt32(text[3]); string map = Convert.ToString(text[5]); int x = Convert.ToInt32(text[7]); int y = Convert.ToInt32(text[9]); // update player Game1.timeOfDay = time; this.Player.previousLocationName = this.Player.currentLocation.name; Game1.locationAfterWarp = Game1.getLocationFromName(map); Game1.xLocationAfterWarp = x; Game1.yLocationAfterWarp = y; //Game1.facingDirectionAfterWarp = this.player_facing_direction; Game1.fadeScreenToBlack(); Game1.warpFarmer(map, x, y, false); //this.Player.faceDirection(this.player_facing_direction); } /// Get whether any data has been saved for this player yet. private bool HasSaveData() { return Directory.Exists(this.SavePath); } } }