Merge pull request #22 from Pathoschild/save-anywhere-fixes
Overhaul Save Anywhere file format, various fixes
This commit is contained in:
commit
7089475d46
|
@ -1,61 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace Omegasis.SaveAnywhere.Framework
|
|
||||||
{
|
|
||||||
/// <summary>Provides methods for reading and writing the config file.</summary>
|
|
||||||
internal class ConfigUtilities
|
|
||||||
{
|
|
||||||
/*********
|
|
||||||
** Properties
|
|
||||||
*********/
|
|
||||||
/// <summary>The full path to the mod folder.</summary>
|
|
||||||
private readonly string ModPath;
|
|
||||||
|
|
||||||
|
|
||||||
/*********
|
|
||||||
** Accessors
|
|
||||||
*********/
|
|
||||||
/// <summary>The key which saves the game.</summary>
|
|
||||||
public string KeyBinding { get; private set; } = "K";
|
|
||||||
|
|
||||||
|
|
||||||
/*********
|
|
||||||
** Public methods
|
|
||||||
*********/
|
|
||||||
/// <summary>Construct an instance.</summary>
|
|
||||||
/// <param name="modPath">The full path to the mod folder.</param>
|
|
||||||
public ConfigUtilities(string modPath)
|
|
||||||
{
|
|
||||||
this.ModPath = modPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Load the configuration settings.</summary>
|
|
||||||
public void LoadConfig()
|
|
||||||
{
|
|
||||||
string path = Path.Combine(this.ModPath, "Save_Anywhere_Config.txt");
|
|
||||||
if (!File.Exists(path))
|
|
||||||
this.KeyBinding = "K";
|
|
||||||
else
|
|
||||||
{
|
|
||||||
string[] text = File.ReadAllLines(path);
|
|
||||||
this.KeyBinding = Convert.ToString(text[3]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Save the configuration settings.</summary>
|
|
||||||
public void WriteConfig()
|
|
||||||
{
|
|
||||||
string path = Path.Combine(this.ModPath, "Save_Anywhere_Config.txt");
|
|
||||||
|
|
||||||
string[] text = new string[20];
|
|
||||||
text[0] = "Config: Save_Anywhere Info. Feel free to mess with these settings.";
|
|
||||||
text[1] = "====================================================================================";
|
|
||||||
|
|
||||||
text[2] = "Key binding for saving anywhere. Press this key to save anywhere!";
|
|
||||||
text[3] = this.KeyBinding;
|
|
||||||
|
|
||||||
File.WriteAllLines(path, text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace Omegasis.SaveAnywhere.Framework
|
||||||
|
{
|
||||||
|
/// <summary>The mod configuration.</summary>
|
||||||
|
internal class ModConfig
|
||||||
|
{
|
||||||
|
/// <summary>The key which initiates a save.</summary>
|
||||||
|
public string SaveKey { get; set; } = "K";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
namespace Omegasis.SaveAnywhere.Framework.Models
|
||||||
|
{
|
||||||
|
/// <summary>The character type for an NPC.</summary>
|
||||||
|
internal enum CharacterType
|
||||||
|
{
|
||||||
|
/// <summary>The player.</summary>
|
||||||
|
Player = 1,
|
||||||
|
|
||||||
|
/// <summary>The player's horse.</summary>
|
||||||
|
Horse = 2,
|
||||||
|
|
||||||
|
/// <summary>The player's pet.</summary>
|
||||||
|
Pet = 3,
|
||||||
|
|
||||||
|
/// <summary>A villager.</summary>
|
||||||
|
Villager = 4
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace Omegasis.SaveAnywhere.Framework.Models
|
||||||
|
{
|
||||||
|
/// <summary>The data for the current player.</summary>
|
||||||
|
internal class PlayerData
|
||||||
|
{
|
||||||
|
/// <summary>The current time.</summary>
|
||||||
|
public int Time { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The saved character data.</summary>
|
||||||
|
public CharacterData[] Characters { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
|
namespace Omegasis.SaveAnywhere.Framework.Models
|
||||||
|
{
|
||||||
|
/// <summary>Represents saved data for an NPC.</summary>
|
||||||
|
internal class CharacterData
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Accessors
|
||||||
|
*********/
|
||||||
|
/// <summary>The character type.</summary>
|
||||||
|
public CharacterType Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The character name.</summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The map name.</summary>
|
||||||
|
public string Map { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The X position.</summary>
|
||||||
|
public int X { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The Y position.</summary>
|
||||||
|
public int Y { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The direction the character is facing.</summary>
|
||||||
|
public int FacingDirection { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Construct an instance.</summary>
|
||||||
|
/// <remarks>This default constructor is needed by Json.NET.</remarks>
|
||||||
|
public CharacterData() { }
|
||||||
|
|
||||||
|
/// <summary>Construct an instance.</summary>
|
||||||
|
/// <param name="type">The character type.</param>
|
||||||
|
/// <param name="name">The character name.</param>
|
||||||
|
/// <param name="map">The map name.</param>
|
||||||
|
/// <param name="x">The X position.</param>
|
||||||
|
/// <param name="y">The Y position.</param>
|
||||||
|
/// <param name="facingDirection">The direction the character is facing.</param>
|
||||||
|
public CharacterData(CharacterType type, string name, string map, int x, int y, int facingDirection)
|
||||||
|
{
|
||||||
|
this.Type = type;
|
||||||
|
this.Name = name;
|
||||||
|
this.Map = map;
|
||||||
|
this.X = x;
|
||||||
|
this.Y = y;
|
||||||
|
this.FacingDirection = facingDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Construct an instance.</summary>
|
||||||
|
/// <param name="type">The character type.</param>
|
||||||
|
/// <param name="name">The character name.</param>
|
||||||
|
/// <param name="map">The map name.</param>
|
||||||
|
/// <param name="tile">The tile position.</param>
|
||||||
|
/// <param name="facingDirection">The direction the character is facing.</param>
|
||||||
|
public CharacterData(CharacterType type, string name, string map, Point tile, int facingDirection)
|
||||||
|
: this(type, name, map, tile.X, tile.Y, facingDirection) { }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using StardewValley.Menus;
|
||||||
|
|
||||||
|
namespace Omegasis.SaveAnywhere.Framework
|
||||||
|
{
|
||||||
|
/// <summary>A marker subclass to detect when a custom save is in progress.</summary>
|
||||||
|
internal class NewSaveGameMenu : SaveGameMenu { }
|
||||||
|
}
|
|
@ -1,13 +1,13 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
|
using Omegasis.SaveAnywhere.Framework.Models;
|
||||||
using StardewModdingAPI;
|
using StardewModdingAPI;
|
||||||
using StardewValley;
|
using StardewValley;
|
||||||
using StardewValley.Characters;
|
using StardewValley.Characters;
|
||||||
using StardewValley.Menus;
|
|
||||||
using StardewValley.Monsters;
|
using StardewValley.Monsters;
|
||||||
using SFarmer = StardewValley.Farmer;
|
|
||||||
|
|
||||||
namespace Omegasis.SaveAnywhere.Framework
|
namespace Omegasis.SaveAnywhere.Framework
|
||||||
{
|
{
|
||||||
|
@ -17,26 +17,17 @@ namespace Omegasis.SaveAnywhere.Framework
|
||||||
/*********
|
/*********
|
||||||
** Properties
|
** Properties
|
||||||
*********/
|
*********/
|
||||||
/// <summary>The player for which to save data.</summary>
|
|
||||||
private readonly SFarmer Player;
|
|
||||||
|
|
||||||
/// <summary>Simplifies access to game code.</summary>
|
/// <summary>Simplifies access to game code.</summary>
|
||||||
private readonly IReflectionHelper Reflection;
|
private readonly IReflectionHelper Reflection;
|
||||||
|
|
||||||
/// <summary>Writes messages to the console and log file.</summary>
|
/// <summary>A callback invoked when data is loaded.</summary>
|
||||||
private readonly IMonitor Monitor;
|
private readonly Action OnLoaded;
|
||||||
|
|
||||||
/// <summary>A callback invoked when villagers are reset during a load.</summary>
|
/// <summary>SMAPI's APIs for this mod.</summary>
|
||||||
private readonly Action OnVillagersReset;
|
private readonly IModHelper Helper;
|
||||||
|
|
||||||
/// <summary>The full path to the folder in which to store data for this player.</summary>
|
/// <summary>The full path to the player data file.</summary>
|
||||||
private readonly string SavePath;
|
private string SavePath => Path.Combine(this.Helper.DirectoryPath, "data", $"{Constants.SaveFolderName}.json");
|
||||||
|
|
||||||
/// <summary>The full path to the folder in which to store animal data for this player.</summary>
|
|
||||||
private readonly string SaveAnimalsPath;
|
|
||||||
|
|
||||||
/// <summary>The full path to the folder in which to store villager data for this player.</summary>
|
|
||||||
private readonly string SaveVillagersPath;
|
|
||||||
|
|
||||||
/// <summary>Whether we should save at the next opportunity.</summary>
|
/// <summary>Whether we should save at the next opportunity.</summary>
|
||||||
private bool WaitingToSave;
|
private bool WaitingToSave;
|
||||||
|
@ -46,23 +37,14 @@ namespace Omegasis.SaveAnywhere.Framework
|
||||||
** Public methods
|
** Public methods
|
||||||
*********/
|
*********/
|
||||||
/// <summary>Construct an instance.</summary>
|
/// <summary>Construct an instance.</summary>
|
||||||
/// <param name="player">The player for which to save data.</param>
|
/// <param name="helper">SMAPI's APIs for this mod.</param>
|
||||||
/// <param name="modPath">The full path to the mod folder.</param>
|
|
||||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
|
||||||
/// <param name="reflection">Simplifies access to game code.</param>
|
/// <param name="reflection">Simplifies access to game code.</param>
|
||||||
/// <param name="onVillagersReset">A callback invoked when villagers are reset during a load.</param>
|
/// <param name="onLoaded">A callback invoked when data is loaded.</param>
|
||||||
public SaveManager(SFarmer player, string modPath, IMonitor monitor, IReflectionHelper reflection, Action onVillagersReset)
|
public SaveManager(IModHelper helper, IReflectionHelper reflection, Action onLoaded)
|
||||||
{
|
{
|
||||||
// save info
|
this.Helper = helper;
|
||||||
this.Player = player;
|
|
||||||
this.Monitor = monitor;
|
|
||||||
this.Reflection = reflection;
|
this.Reflection = reflection;
|
||||||
this.OnVillagersReset = onVillagersReset;
|
this.OnLoaded = onLoaded;
|
||||||
|
|
||||||
// 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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Perform any required update logic.</summary>
|
/// <summary>Perform any required update logic.</summary>
|
||||||
|
@ -71,13 +53,20 @@ namespace Omegasis.SaveAnywhere.Framework
|
||||||
// perform passive save
|
// perform passive save
|
||||||
if (this.WaitingToSave && Game1.activeClickableMenu == null)
|
if (this.WaitingToSave && Game1.activeClickableMenu == null)
|
||||||
{
|
{
|
||||||
Game1.activeClickableMenu = new SaveGameMenu();
|
Game1.activeClickableMenu = new NewSaveGameMenu();
|
||||||
this.WaitingToSave = false;
|
this.WaitingToSave = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Save all game data.</summary>
|
/// <summary>Clear saved data.</summary>
|
||||||
public void SaveGameAndPositions()
|
public void ClearData()
|
||||||
|
{
|
||||||
|
Directory.Delete(this.SavePath, recursive: true);
|
||||||
|
this.RemoveLegacyDataForThisPlayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Initiate a game save.</summary>
|
||||||
|
public void BeginSaveData()
|
||||||
{
|
{
|
||||||
// save game data
|
// save game data
|
||||||
Farm farm = Game1.getFarm();
|
Farm farm = Game1.getFarm();
|
||||||
|
@ -89,280 +78,134 @@ namespace Omegasis.SaveAnywhere.Framework
|
||||||
this.WaitingToSave = true;
|
this.WaitingToSave = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
Game1.activeClickableMenu = new SaveGameMenu();
|
Game1.activeClickableMenu = new NewSaveGameMenu();
|
||||||
|
|
||||||
// save custom data
|
// get data
|
||||||
Directory.CreateDirectory(this.SaveAnimalsPath);
|
PlayerData data = new PlayerData
|
||||||
Directory.CreateDirectory(this.SaveVillagersPath);
|
{
|
||||||
this.SavePlayerPosition();
|
Time = Game1.timeOfDay,
|
||||||
this.SaveHorsePosition();
|
Characters = this.GetPositions().ToArray()
|
||||||
this.SavePetPosition();
|
};
|
||||||
this.SaveVillagerPositions();
|
|
||||||
|
// save to disk
|
||||||
|
// ReSharper disable once PossibleNullReferenceException -- not applicable
|
||||||
|
Directory.CreateDirectory(new FileInfo(this.SavePath).Directory.FullName);
|
||||||
|
this.Helper.WriteJsonFile(this.SavePath, data);
|
||||||
|
|
||||||
|
// clear any legacy data (no longer needed as backup)
|
||||||
|
this.RemoveLegacyDataForThisPlayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Load all game data.</summary>
|
/// <summary>Load all game data.</summary>
|
||||||
public void LoadPositions()
|
public void LoadData()
|
||||||
{
|
{
|
||||||
if (!this.HasSaveData())
|
// get data
|
||||||
|
PlayerData data = this.Helper.ReadJsonFile<PlayerData>(this.SavePath);
|
||||||
|
if (data == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.LoadPlayerPosition();
|
// apply
|
||||||
this.LoadHorsePosition();
|
Game1.timeOfDay = data.Time;
|
||||||
this.LoadPetPosition();
|
this.SetPositions(data.Characters);
|
||||||
bool anyVillagersMoved = this.LoadVillagerPositions();
|
this.OnLoaded?.Invoke();
|
||||||
|
|
||||||
if (anyVillagersMoved)
|
|
||||||
this.OnVillagersReset?.Invoke();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
** Private methods
|
** Private methods
|
||||||
*********/
|
*********/
|
||||||
/// <summary>Save the horse state to the save file.</summary>
|
/// <summary>Get the current character positions.</summary>
|
||||||
private void SaveHorsePosition()
|
private IEnumerable<CharacterData> GetPositions()
|
||||||
{
|
{
|
||||||
// find horse
|
// player
|
||||||
Horse horse = Utility.findHorse();
|
{
|
||||||
if (horse == null)
|
var player = Game1.player;
|
||||||
return;
|
string name = player.name;
|
||||||
|
string map = player.currentLocation.name;
|
||||||
|
Point tile = player.getTileLocationPoint();
|
||||||
|
int facingDirection = player.facingDirection;
|
||||||
|
|
||||||
// get horse info
|
yield return new CharacterData(CharacterType.Player, name, map, tile, facingDirection);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Reset the horse to the saved state.</summary>
|
// NPCs (including horse and pets)
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Save the villager states to the save file.</summary>
|
|
||||||
private void SaveVillagerPositions()
|
|
||||||
{
|
|
||||||
foreach (NPC npc in Utility.getAllCharacters())
|
foreach (NPC npc in Utility.getAllCharacters())
|
||||||
{
|
{
|
||||||
// ignore non-villagers
|
CharacterType? type = this.GetCharacterType(npc);
|
||||||
if (npc is Pet || npc is Monster)
|
if (type == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// get NPC data
|
|
||||||
string name = npc.name;
|
string name = npc.name;
|
||||||
string map = npc.currentLocation.name;
|
string map = npc.currentLocation.name;
|
||||||
Point tile = npc.getTileLocationPoint();
|
Point tile = npc.getTileLocationPoint();
|
||||||
|
int facingDirection = npc.facingDirection;
|
||||||
|
|
||||||
// save data
|
yield return new CharacterData(type.Value, name, map, tile, facingDirection);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Reset the villagers to their saved state.</summary>
|
/// <summary>Reset characters to their saved state.</summary>
|
||||||
/// <returns>Returns whether any villagers changed position.</returns>
|
/// <param name="positions">The positions to set.</param>
|
||||||
private bool LoadVillagerPositions()
|
/// <returns>Returns whether any NPCs changed position.</returns>
|
||||||
|
private void SetPositions(CharacterData[] positions)
|
||||||
{
|
{
|
||||||
bool anyLoaded = false;
|
// player
|
||||||
|
{
|
||||||
|
CharacterData data = positions.FirstOrDefault(p => p.Type == CharacterType.Player && p.Name == Game1.player.name);
|
||||||
|
if (data != null)
|
||||||
|
{
|
||||||
|
Game1.player.previousLocationName = Game1.player.currentLocation.name;
|
||||||
|
Game1.locationAfterWarp = Game1.getLocationFromName(data.Name);
|
||||||
|
Game1.xLocationAfterWarp = data.X;
|
||||||
|
Game1.yLocationAfterWarp = data.Y;
|
||||||
|
Game1.facingDirectionAfterWarp = data.FacingDirection;
|
||||||
|
Game1.fadeScreenToBlack();
|
||||||
|
Game1.warpFarmer(data.Map, data.X, data.Y, false);
|
||||||
|
Game1.player.faceDirection(data.FacingDirection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NPCs (including horse and pets)
|
||||||
foreach (NPC npc in Utility.getAllCharacters())
|
foreach (NPC npc in Utility.getAllCharacters())
|
||||||
{
|
{
|
||||||
// ignore non-villagers
|
// get NPC type
|
||||||
if (npc is Pet || npc is Monster)
|
CharacterType? type = this.GetCharacterType(npc);
|
||||||
|
if (type == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// get file path
|
// get saved data
|
||||||
string path = Path.Combine(this.SaveVillagersPath, npc.name + ".txt");
|
CharacterData data = positions.FirstOrDefault(p => p.Type == type && p.Name == npc.name);
|
||||||
if (!File.Exists(path))
|
if (data == null)
|
||||||
{
|
|
||||||
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;
|
continue;
|
||||||
|
|
||||||
// update NPC
|
// update NPC
|
||||||
anyLoaded = true;
|
Game1.warpCharacter(npc, data.Map, new Point(data.X, data.Y), false, true);
|
||||||
Game1.warpCharacter(npc, map, new Point(x, y), false, true);
|
npc.faceDirection(data.FacingDirection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return anyLoaded;
|
/// <summary>Get the character type for an NPC.</summary>
|
||||||
}
|
/// <param name="npc">The NPC to check.</param>
|
||||||
|
private CharacterType? GetCharacterType(NPC npc)
|
||||||
/// <summary>Save the pet state to the save file.</summary>
|
|
||||||
private void SavePetPosition()
|
|
||||||
{
|
{
|
||||||
if (!this.Player.hasPet())
|
if (npc is Monster)
|
||||||
return;
|
return null;
|
||||||
|
if (npc is Horse)
|
||||||
// find pet
|
return CharacterType.Horse;
|
||||||
Pet pet = Utility.getAllCharacters().OfType<Pet>().FirstOrDefault();
|
if (npc is Pet)
|
||||||
if (pet == null)
|
return CharacterType.Pet;
|
||||||
return;
|
return CharacterType.Villager;
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Reset the pet to the saved state.</summary>
|
/// <summary>Remove legacy save data for this player.</summary>
|
||||||
private void LoadPetPosition()
|
private void RemoveLegacyDataForThisPlayer()
|
||||||
{
|
{
|
||||||
if (!this.Player.hasPet())
|
DirectoryInfo dataDir = new DirectoryInfo(Path.Combine(this.Helper.DirectoryPath, "Save_Data"));
|
||||||
return;
|
DirectoryInfo playerDir = new DirectoryInfo(Path.Combine(dataDir.FullName, Game1.player.name));
|
||||||
|
if (playerDir.Exists)
|
||||||
// find pet
|
playerDir.Delete(recursive: true);
|
||||||
Pet pet = Utility.getAllCharacters().OfType<Pet>().FirstOrDefault();
|
if (dataDir.Exists && !dataDir.EnumerateDirectories().Any())
|
||||||
if (pet == null)
|
dataDir.Delete(recursive: true);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Save the player state to the save file.</summary>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Reset the player to the saved state.</summary>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Get whether any data has been saved for this player yet.</summary>
|
|
||||||
private bool HasSaveData()
|
|
||||||
{
|
|
||||||
return Directory.Exists(this.SavePath);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,4 +37,12 @@ Press `K` to save anywhere. Edit `Save_Anywhere_Config.txt` to configure the key
|
||||||
|
|
||||||
2.5:
|
2.5:
|
||||||
* Updated for SMAPI 2.0.
|
* Updated for SMAPI 2.0.
|
||||||
|
* Overhauled save format.
|
||||||
|
* Switched to standard JSON config file.
|
||||||
|
* Fixed crash when saving in the community center.
|
||||||
|
* Fixed crash during cutscenes.
|
||||||
|
* Fixed load warp only happening after you move.
|
||||||
|
* Fixed load not working after you exit to title.
|
||||||
|
* Fixed some old data being restored if you reload after a normal save.
|
||||||
|
* Fixed player/NPC facing directions not being restored.
|
||||||
* Internal refactoring.
|
* Internal refactoring.
|
||||||
|
|
|
@ -15,18 +15,20 @@ namespace Omegasis.SaveAnywhere
|
||||||
/*********
|
/*********
|
||||||
** Properties
|
** Properties
|
||||||
*********/
|
*********/
|
||||||
|
/// <summary>The mod configuration.</summary>
|
||||||
|
private ModConfig Config;
|
||||||
|
|
||||||
/// <summary>Provides methods for saving and loading game data.</summary>
|
/// <summary>Provides methods for saving and loading game data.</summary>
|
||||||
private SaveManager SaveManager;
|
private SaveManager SaveManager;
|
||||||
|
|
||||||
/// <summary>Provides methods for reading and writing the config file.</summary>
|
|
||||||
private ConfigUtilities ConfigUtilities;
|
|
||||||
|
|
||||||
/// <summary>The parsed schedules by NPC name.</summary>
|
/// <summary>The parsed schedules by NPC name.</summary>
|
||||||
private readonly IDictionary<string, string> NpcSchedules = new Dictionary<string, string>();
|
private readonly IDictionary<string, string> NpcSchedules = new Dictionary<string, string>();
|
||||||
|
|
||||||
/// <summary>Whether villager schedules should be reset now.</summary>
|
/// <summary>Whether villager schedules should be reset now.</summary>
|
||||||
private bool ShouldResetSchedules;
|
private bool ShouldResetSchedules;
|
||||||
|
|
||||||
|
/// <summary>Whether we're performing a non-vanilla save (i.e. not by sleeping in bed).</summary>
|
||||||
|
private bool IsCustomSaving;
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
|
@ -36,9 +38,11 @@ namespace Omegasis.SaveAnywhere
|
||||||
/// <param name="helper">Provides simplified APIs for writing mods.</param>
|
/// <param name="helper">Provides simplified APIs for writing mods.</param>
|
||||||
public override void Entry(IModHelper helper)
|
public override void Entry(IModHelper helper)
|
||||||
{
|
{
|
||||||
this.ConfigUtilities = new ConfigUtilities(this.Helper.DirectoryPath);
|
this.Config = helper.ReadConfig<ModConfig>();
|
||||||
|
|
||||||
SaveEvents.AfterLoad += this.SaveEvents_AfterLoad;
|
SaveEvents.AfterLoad += this.SaveEvents_AfterLoad;
|
||||||
|
SaveEvents.AfterSave += this.SaveEvents_AfterSave;
|
||||||
|
MenuEvents.MenuChanged += this.MenuEvents_MenuChanged;
|
||||||
ControlEvents.KeyPressed += this.ControlEvents_KeyPressed;
|
ControlEvents.KeyPressed += this.ControlEvents_KeyPressed;
|
||||||
GameEvents.UpdateTick += this.GameEvents_UpdateTick;
|
GameEvents.UpdateTick += this.GameEvents_UpdateTick;
|
||||||
TimeEvents.AfterDayStarted += this.TimeEvents_AfterDayStarted;
|
TimeEvents.AfterDayStarted += this.TimeEvents_AfterDayStarted;
|
||||||
|
@ -54,15 +58,30 @@ namespace Omegasis.SaveAnywhere
|
||||||
private void SaveEvents_AfterLoad(object sender, EventArgs e)
|
private void SaveEvents_AfterLoad(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
// reset state
|
// reset state
|
||||||
|
this.IsCustomSaving = false;
|
||||||
this.ShouldResetSchedules = false;
|
this.ShouldResetSchedules = false;
|
||||||
|
|
||||||
// load config
|
|
||||||
this.ConfigUtilities.LoadConfig();
|
|
||||||
this.ConfigUtilities.WriteConfig();
|
|
||||||
|
|
||||||
// load positions
|
// load positions
|
||||||
this.SaveManager = new SaveManager(Game1.player, this.Helper.DirectoryPath, this.Monitor, this.Helper.Reflection, onVillagersReset: () => this.ShouldResetSchedules = true);
|
this.SaveManager = new SaveManager(this.Helper, this.Helper.Reflection, onLoaded: () => this.ShouldResetSchedules = true);
|
||||||
this.SaveManager.LoadPositions();
|
this.SaveManager.LoadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>The method invoked after the player finishes saving.</summary>
|
||||||
|
/// <param name="sender">The event sender.</param>
|
||||||
|
/// <param name="e">The event data.</param>
|
||||||
|
private void SaveEvents_AfterSave(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// clear custom data after a normal save (to avoid restoring old state)
|
||||||
|
if (!this.IsCustomSaving)
|
||||||
|
this.SaveManager.ClearData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>The method invoked after a menu is opened or changed.</summary>
|
||||||
|
/// <param name="sender">The event sender.</param>
|
||||||
|
/// <param name="e">The event data.</param>
|
||||||
|
private void MenuEvents_MenuChanged(object sender, EventArgsClickableMenuChanged e)
|
||||||
|
{
|
||||||
|
this.IsCustomSaving = e.NewMenu != null && (e.NewMenu is NewSaveGameMenu || e.NewMenu is NewShippingMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>The method invoked when the game updates (roughly 60 times per second).</summary>
|
/// <summary>The method invoked when the game updates (roughly 60 times per second).</summary>
|
||||||
|
@ -107,8 +126,19 @@ namespace Omegasis.SaveAnywhere
|
||||||
if (!Context.IsPlayerFree)
|
if (!Context.IsPlayerFree)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (e.KeyPressed.ToString() == this.ConfigUtilities.KeyBinding)
|
// initiate save (if valid context)
|
||||||
this.SaveManager.SaveGameAndPositions();
|
if (e.KeyPressed.ToString() == this.Config.SaveKey)
|
||||||
|
{
|
||||||
|
// validate: community center Junimos can't be saved
|
||||||
|
if (Utility.getAllCharacters().OfType<Junimo>().Any())
|
||||||
|
{
|
||||||
|
Game1.addHUDMessage(new HUDMessage("The spirits don't want you to save here.", HUDMessage.error_type));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// save
|
||||||
|
this.SaveManager.BeginSaveData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Apply the NPC schedules to each NPC.</summary>
|
/// <summary>Apply the NPC schedules to each NPC.</summary>
|
||||||
|
|
|
@ -37,8 +37,12 @@
|
||||||
<Compile Include="..\GlobalAssemblyInfo.cs">
|
<Compile Include="..\GlobalAssemblyInfo.cs">
|
||||||
<Link>Properties\GlobalAssemblyInfo.cs</Link>
|
<Link>Properties\GlobalAssemblyInfo.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Framework\ModConfig.cs" />
|
||||||
|
<Compile Include="Framework\Models\CharacterType.cs" />
|
||||||
|
<Compile Include="Framework\Models\PositionData.cs" />
|
||||||
|
<Compile Include="Framework\NewSaveMenu.cs" />
|
||||||
|
<Compile Include="Framework\Models\PlayerData.cs" />
|
||||||
<Compile Include="Framework\SaveManager.cs" />
|
<Compile Include="Framework\SaveManager.cs" />
|
||||||
<Compile Include="Framework\ConfigUtilities.cs" />
|
|
||||||
<Compile Include="SaveAnywhere.cs" />
|
<Compile Include="SaveAnywhere.cs" />
|
||||||
<Compile Include="Framework\NewShippingMenu.cs" />
|
<Compile Include="Framework\NewShippingMenu.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
|
Loading…
Reference in New Issue