using System; using System.Collections.Generic; using System.Linq; using Omegasis.SaveAnywhere.Framework; using StardewModdingAPI; using StardewModdingAPI.Events; using StardewValley; using StardewValley.Characters; using StardewValley.Monsters; namespace Omegasis.SaveAnywhere { /// The mod entry point. public class SaveAnywhere : Mod { public static SaveAnywhere Instance; /********* ** Fields *********/ /// The mod configuration. private ModConfig Config; /// Provides methods for saving and loading game data. public SaveManager SaveManager; /// The parsed schedules by NPC name. private readonly IDictionary NpcSchedules = new Dictionary(); /// Whether villager schedules should be reset now. private bool ShouldResetSchedules; /// Whether we're performing a non-vanilla save (i.e. not by sleeping in bed). public bool IsCustomSaving; /// Used to access the Mod's helper from other files associated with the mod. public static IModHelper ModHelper; /// Used to access the Mod's monitor to allow for debug logging in other files associated with the mod. public static IMonitor ModMonitor; private Dictionary> monsters; private bool customMenuOpen; private bool firstLoad; /********* ** Public methods *********/ /// The mod entry point, called after the mod is first loaded. /// Provides simplified APIs for writing mods. public override void Entry(IModHelper helper) { this.Config = helper.ReadConfig(); this.SaveManager = new SaveManager(this.Helper, this.Helper.Reflection, onLoaded: () => this.ShouldResetSchedules = true); helper.Events.GameLoop.SaveLoaded += this.OnSaveLoaded; helper.Events.GameLoop.UpdateTicked += this.OnUpdateTicked; helper.Events.GameLoop.DayStarted += this.OnDayStarted; helper.Events.Input.ButtonPressed += this.OnButtonPressed; helper.Events.GameLoop.ReturnedToTitle += this.GameLoop_ReturnedToTitle; helper.Events.GameLoop.TimeChanged += this.GameLoop_TimeChanged; ModHelper = helper; ModMonitor = this.Monitor; this.customMenuOpen = false; Instance = this; this.firstLoad = false; } private void GameLoop_TimeChanged(object sender, TimeChangedEventArgs e) { //throw new NotImplementedException(); } private void GameLoop_ReturnedToTitle(object sender, ReturnedToTitleEventArgs e) { this.firstLoad = false; } /********* ** Private methods *********/ /// Raised after the player loads a save slot and the world is initialised. /// The event sender. /// The event arguments. private void OnSaveLoaded(object sender, SaveLoadedEventArgs e) { // reset state this.ShouldResetSchedules = false; // load positions this.SaveManager.LoadData(); //this.SaveManager.ClearData(); } /// Raised after the game state is updated (≈60 times per second). /// The event sender. /// The event arguments. private void OnUpdateTicked(object sender, UpdateTickedEventArgs e) { // let save manager run background logic if (Context.IsWorldReady) { if (!Game1.player.IsMainPlayer) return; this.SaveManager.Update(); } if (Game1.activeClickableMenu == null && Context.IsWorldReady) { this.IsCustomSaving = false; } if (Game1.activeClickableMenu == null && !this.customMenuOpen) return; if (Game1.activeClickableMenu == null && this.customMenuOpen) { this.customMenuOpen = false; return; } if (Game1.activeClickableMenu != null) { if (Game1.activeClickableMenu.GetType() == typeof(NewSaveGameMenuV2)) { this.customMenuOpen = true; } } } /// Saves all monsters from the game world. public void cleanMonsters() { this.monsters = new Dictionary>(); foreach (GameLocation loc in Game1.locations) { this.monsters.Add(loc, new List()); foreach (var npc in loc.characters) { if (npc is Monster monster) { this.Monitor.Log(npc.Name); this.monsters[loc].Add(monster); } } foreach (var monster in this.monsters[loc]) loc.characters.Remove(monster); } } /// Adds all saved monster back into the game world. public static void RestoreMonsters() { foreach (var pair in SaveAnywhere.Instance.monsters) { foreach (Monster m in pair.Value) { pair.Key.addCharacter(m); } } SaveAnywhere.Instance.monsters.Clear(); } /// Raised after the game begins a new day (including when the player loads a save). /// The event sender. /// The event arguments. private void OnDayStarted(object sender, DayStartedEventArgs e) { //this.Monitor.Log("On day started called.", LogLevel.Info); if (this.IsCustomSaving == false) { if (this.firstLoad == false) { this.firstLoad = true; if (this.SaveManager.saveDataExists()) { this.ShouldResetSchedules = false; this.ApplySchedules(); } } else if (this.firstLoad == true) { this.SaveManager.ClearData(); //Clean the save state on consecutive days to ensure save files aren't lost inbetween incase the player accidently quits. } //this.Monitor.Log("Cleaning old save file.", LogLevel.Info); // reload NPC schedules this.ShouldResetSchedules = true; // reset NPC schedules /* // update NPC schedules this.NpcSchedules.Clear(); foreach (NPC npc in Utility.getAllCharacters()) { if (!this.NpcSchedules.ContainsKey(npc.Name)) this.NpcSchedules.Add(npc.Name, this.ParseSchedule(npc)); } */ } } /// Raised after the player presses a button on the keyboard, controller, or mouse. /// The event sender. /// The event arguments. private void OnButtonPressed(object sender, ButtonPressedEventArgs e) { if (!Context.IsPlayerFree) return; // initiate save (if valid context) if (e.Button == this.Config.SaveKey) { if (Game1.eventUp) return; if (Game1.isFestival()) return; if (Game1.client == null) { // validate: community center Junimos can't be saved if (Game1.player.currentLocation.getCharacters().OfType().Any()) { Game1.addHUDMessage(new HUDMessage("The spirits don't want you to save here.", HUDMessage.error_type)); return; } // save this.IsCustomSaving = true; this.SaveManager.BeginSaveData(); } else { Game1.addHUDMessage(new HUDMessage("Only server hosts can save anywhere.", HUDMessage.error_type)); } } } /// Apply the NPC schedules to each NPC. private void ApplySchedules() { if (Game1.weatherIcon == Game1.weather_festival || Game1.isFestival() || Game1.eventUp) return; // apply for each NPC foreach (GameLocation loc in Game1.locations) { foreach (NPC npc in loc.characters) { if (npc.isVillager() == false) continue; npc.fillInSchedule(); continue; } } } public override object GetApi() { return new SaveAnywhereAPI(); } } }