2017-07-31 05:42:15 +08:00
using System ;
2017-08-06 11:19:38 +08:00
using System.Collections.Generic ;
2017-07-31 05:42:15 +08:00
using System.IO ;
using System.Linq ;
using Microsoft.Xna.Framework ;
2017-08-06 11:19:38 +08:00
using Omegasis.SaveAnywhere.Framework.Models ;
2017-07-31 05:42:15 +08:00
using StardewModdingAPI ;
using StardewValley ;
using StardewValley.Characters ;
2017-07-31 09:58:48 +08:00
using StardewValley.Monsters ;
2017-07-31 05:42:15 +08:00
2017-07-31 11:07:07 +08:00
namespace Omegasis.SaveAnywhere.Framework
2017-07-31 05:42:15 +08:00
{
/// <summary>Provides methods for saving and loading game data.</summary>
2017-07-31 05:57:00 +08:00
internal class SaveManager
2017-07-31 05:42:15 +08:00
{
2017-07-31 05:57:00 +08:00
/ * * * * * * * * *
* * Properties
* * * * * * * * * /
2017-07-31 06:26:22 +08:00
/// <summary>Simplifies access to game code.</summary>
private readonly IReflectionHelper Reflection ;
2017-08-06 11:19:38 +08:00
/// <summary>A callback invoked when data is loaded.</summary>
private readonly Action OnLoaded ;
2017-07-31 09:58:48 +08:00
2017-08-06 11:19:38 +08:00
/// <summary>SMAPI's APIs for this mod.</summary>
private readonly IModHelper Helper ;
2017-07-31 09:58:48 +08:00
2017-08-06 11:19:38 +08:00
/// <summary>The full path to the player data file.</summary>
private string SavePath = > Path . Combine ( this . Helper . DirectoryPath , "data" , $"{Constants.SaveFolderName}.json" ) ;
2017-07-31 09:58:48 +08:00
/// <summary>Whether we should save at the next opportunity.</summary>
private bool WaitingToSave ;
2017-07-31 05:57:00 +08:00
/ * * * * * * * * *
* * Public methods
* * * * * * * * * /
2017-07-31 06:26:22 +08:00
/// <summary>Construct an instance.</summary>
2017-08-06 11:19:38 +08:00
/// <param name="helper">SMAPI's APIs for this mod.</param>
2017-07-31 06:26:22 +08:00
/// <param name="reflection">Simplifies access to game code.</param>
2017-08-06 11:19:38 +08:00
/// <param name="onLoaded">A callback invoked when data is loaded.</param>
public SaveManager ( IModHelper helper , IReflectionHelper reflection , Action onLoaded )
2017-07-31 06:26:22 +08:00
{
2017-08-06 11:19:38 +08:00
this . Helper = helper ;
2017-07-31 06:26:22 +08:00
this . Reflection = reflection ;
2017-08-06 11:19:38 +08:00
this . OnLoaded = onLoaded ;
2017-07-31 06:26:22 +08:00
}
2017-07-31 09:58:48 +08:00
/// <summary>Perform any required update logic.</summary>
public void Update ( )
2017-07-31 05:42:15 +08:00
{
2017-07-31 09:58:48 +08:00
// perform passive save
if ( this . WaitingToSave & & Game1 . activeClickableMenu = = null )
2017-07-31 05:42:15 +08:00
{
2017-08-06 11:17:51 +08:00
Game1 . activeClickableMenu = new NewSaveGameMenu ( ) ;
2017-07-31 09:58:48 +08:00
this . WaitingToSave = false ;
2017-07-31 05:42:15 +08:00
}
}
2017-08-06 11:17:51 +08:00
/// <summary>Clear saved data.</summary>
public void ClearData ( )
{
Directory . Delete ( this . SavePath , recursive : true ) ;
2017-08-06 11:19:38 +08:00
this . RemoveLegacyDataForThisPlayer ( ) ;
2017-08-06 11:17:51 +08:00
}
2017-08-06 11:19:38 +08:00
/// <summary>Initiate a game save.</summary>
public void BeginSaveData ( )
2017-07-31 05:42:15 +08:00
{
2017-07-31 09:58:48 +08:00
// save game data
Farm farm = Game1 . getFarm ( ) ;
if ( farm . shippingBin . Any ( ) )
2017-07-31 05:42:15 +08:00
{
2017-07-31 09:58:48 +08:00
Game1 . activeClickableMenu = new NewShippingMenu ( farm . shippingBin , this . Reflection ) ;
farm . shippingBin . Clear ( ) ;
farm . lastItemShipped = null ;
this . WaitingToSave = true ;
2017-07-31 05:42:15 +08:00
}
2017-07-31 09:58:48 +08:00
else
2017-08-06 11:17:51 +08:00
Game1 . activeClickableMenu = new NewSaveGameMenu ( ) ;
2017-07-31 05:42:15 +08:00
2017-08-06 11:19:38 +08:00
// get data
PlayerData data = new PlayerData
{
Time = Game1 . timeOfDay ,
Characters = this . GetPositions ( ) . ToArray ( )
} ;
// 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 ( ) ;
2017-07-31 05:42:15 +08:00
}
2017-07-31 09:58:48 +08:00
/// <summary>Load all game data.</summary>
2017-08-06 11:19:38 +08:00
public void LoadData ( )
2017-07-31 05:42:15 +08:00
{
2017-08-06 11:19:38 +08:00
// get data
PlayerData data = this . Helper . ReadJsonFile < PlayerData > ( this . SavePath ) ;
if ( data = = null )
2017-07-31 09:58:48 +08:00
return ;
2017-07-31 05:42:15 +08:00
2017-08-06 11:19:38 +08:00
// apply
Game1 . timeOfDay = data . Time ;
this . SetPositions ( data . Characters ) ;
this . OnLoaded ? . Invoke ( ) ;
2017-07-31 09:58:48 +08:00
}
2017-07-31 05:42:15 +08:00
2017-07-31 09:58:48 +08:00
/ * * * * * * * * *
* * Private methods
* * * * * * * * * /
2017-08-06 11:19:38 +08:00
/// <summary>Get the current character positions.</summary>
private IEnumerable < CharacterData > GetPositions ( )
2017-07-31 05:42:15 +08:00
{
2017-08-06 11:19:38 +08:00
// player
{
var player = Game1 . player ;
string name = player . name ;
2017-08-21 01:38:59 +08:00
string map = player . currentLocation . uniqueName ; //Try to get a unique name for the location and if we can't we are going to default to the actual name of the map.
if ( map = = "" | | map = = null ) map = player . currentLocation . name ; //This is used to account for maps that share the same name but have a unique ID such as Coops, Barns and Sheds.
2017-08-06 11:19:38 +08:00
Point tile = player . getTileLocationPoint ( ) ;
int facingDirection = player . facingDirection ;
2017-07-31 05:42:15 +08:00
2017-08-06 11:19:38 +08:00
yield return new CharacterData ( CharacterType . Player , name , map , tile , facingDirection ) ;
}
2017-07-31 05:42:15 +08:00
2017-08-06 11:19:38 +08:00
// NPCs (including horse and pets)
2017-07-31 09:58:48 +08:00
foreach ( NPC npc in Utility . getAllCharacters ( ) )
2017-07-31 05:42:15 +08:00
{
2017-08-06 11:19:38 +08:00
CharacterType ? type = this . GetCharacterType ( npc ) ;
if ( type = = null )
2017-07-31 09:58:48 +08:00
continue ;
2017-07-31 05:42:15 +08:00
2017-07-31 09:58:48 +08:00
string name = npc . name ;
string map = npc . currentLocation . name ;
Point tile = npc . getTileLocationPoint ( ) ;
2017-08-06 11:19:38 +08:00
int facingDirection = npc . facingDirection ;
2017-07-31 05:42:15 +08:00
2017-08-06 11:19:38 +08:00
yield return new CharacterData ( type . Value , name , map , tile , facingDirection ) ;
2017-07-31 09:58:48 +08:00
}
2017-07-31 05:42:15 +08:00
}
2017-08-06 11:19:38 +08:00
/// <summary>Reset characters to their saved state.</summary>
/// <param name="positions">The positions to set.</param>
/// <returns>Returns whether any NPCs changed position.</returns>
private void SetPositions ( CharacterData [ ] positions )
2017-07-31 05:42:15 +08:00
{
2017-08-06 11:19:38 +08:00
// player
2017-07-31 05:42:15 +08:00
{
2017-08-06 11:19:38 +08:00
CharacterData data = positions . FirstOrDefault ( p = > p . Type = = CharacterType . Player & & p . Name = = Game1 . player . name ) ;
if ( data ! = null )
2017-07-31 05:42:15 +08:00
{
2017-08-06 11:19:38 +08:00
Game1 . player . previousLocationName = Game1 . player . currentLocation . name ;
2017-08-21 01:38:59 +08:00
Game1 . locationAfterWarp = Game1 . getLocationFromName ( data . Map ) ;
2017-08-06 11:19:38 +08:00
Game1 . xLocationAfterWarp = data . X ;
Game1 . yLocationAfterWarp = data . Y ;
Game1 . facingDirectionAfterWarp = data . FacingDirection ;
2017-08-21 01:38:59 +08:00
Game1 . fadeScreenToBlack ( ) ;
2017-08-06 11:19:38 +08:00
Game1 . warpFarmer ( data . Map , data . X , data . Y , false ) ;
Game1 . player . faceDirection ( data . FacingDirection ) ;
2017-07-31 05:42:15 +08:00
}
2017-08-06 11:19:38 +08:00
}
2017-07-31 05:42:15 +08:00
2017-08-06 11:46:29 +08:00
// NPCs (including horse and pets)
2017-08-06 11:19:38 +08:00
foreach ( NPC npc in Utility . getAllCharacters ( ) )
{
// get NPC type
CharacterType ? type = this . GetCharacterType ( npc ) ;
if ( type = = null )
continue ;
// get saved data
CharacterData data = positions . FirstOrDefault ( p = > p . Type = = type & & p . Name = = npc . name ) ;
if ( data = = null )
2017-07-31 09:58:48 +08:00
continue ;
2017-07-31 05:42:15 +08:00
2017-07-31 09:58:48 +08:00
// update NPC
2017-08-06 11:19:38 +08:00
Game1 . warpCharacter ( npc , data . Map , new Point ( data . X , data . Y ) , false , true ) ;
npc . faceDirection ( data . FacingDirection ) ;
2017-07-31 05:42:15 +08:00
}
}
2017-08-06 11:19:38 +08:00
/// <summary>Get the character type for an NPC.</summary>
/// <param name="npc">The NPC to check.</param>
private CharacterType ? GetCharacterType ( NPC npc )
2017-07-31 05:42:15 +08:00
{
2017-08-06 11:19:38 +08:00
if ( npc is Monster )
return null ;
if ( npc is Horse )
return CharacterType . Horse ;
if ( npc is Pet )
return CharacterType . Pet ;
return CharacterType . Villager ;
2017-07-31 05:42:15 +08:00
}
2017-08-06 11:19:38 +08:00
/// <summary>Remove legacy save data for this player.</summary>
private void RemoveLegacyDataForThisPlayer ( )
2017-07-31 05:42:15 +08:00
{
2017-08-06 11:19:38 +08:00
DirectoryInfo dataDir = new DirectoryInfo ( Path . Combine ( this . Helper . DirectoryPath , "Save_Data" ) ) ;
DirectoryInfo playerDir = new DirectoryInfo ( Path . Combine ( dataDir . FullName , Game1 . player . name ) ) ;
if ( playerDir . Exists )
playerDir . Delete ( recursive : true ) ;
if ( dataDir . Exists & & ! dataDir . EnumerateDirectories ( ) . Any ( ) )
dataDir . Delete ( recursive : true ) ;
2017-07-31 05:42:15 +08:00
}
}
}