2018-12-30 18:00:05 +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>
2018-06-23 15:31:40 +08:00
public class SaveManager
2017-07-31 05:42:15 +08:00
{
2017-07-31 05:57:00 +08:00
/ * * * * * * * * *
2019-01-06 15:23:07 +08:00
* * Fields
2017-07-31 05:57:00 +08:00
* * * * * * * * * /
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
2019-01-06 13:31:40 +08:00
/// <summary>The relative path to the player data file.</summary>
private string RelativeDataPath = > Path . Combine ( "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
2018-06-23 15:31:40 +08:00
/// <summary> Currently displayed save menu (null if no menu is displayed) </summary>
private NewSaveGameMenu currentSaveMenu ;
2018-09-22 01:46:11 +08:00
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 ;
2018-08-07 05:01:44 +08:00
}
2018-12-30 18:00:05 +08:00
private void empty ( object o , EventArgs args ) { }
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
{
2018-12-30 18:00:05 +08:00
this . currentSaveMenu = new NewSaveGameMenu ( ) ;
this . currentSaveMenu . SaveComplete + = this . CurrentSaveMenu_SaveComplete ;
Game1 . activeClickableMenu = this . currentSaveMenu ;
2017-07-31 09:58:48 +08:00
this . WaitingToSave = false ;
2017-07-31 05:42:15 +08:00
}
}
2018-12-30 18:00:05 +08:00
/// <summary>Event function for NewSaveGameMenu event SaveComplete</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
2018-06-23 15:31:40 +08:00
private void CurrentSaveMenu_SaveComplete ( object sender , EventArgs e )
{
2018-12-30 18:00:05 +08:00
this . currentSaveMenu . SaveComplete - = this . CurrentSaveMenu_SaveComplete ;
this . currentSaveMenu = null ;
2018-09-22 01:46:11 +08:00
//AfterSave.Invoke(this, EventArgs.Empty);
2018-06-23 15:31:40 +08:00
}
2017-08-06 11:17:51 +08:00
/// <summary>Clear saved data.</summary>
public void ClearData ( )
{
2019-01-06 13:31:40 +08:00
File . Delete ( Path . Combine ( this . Helper . DirectoryPath , this . RelativeDataPath ) ) ;
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
{
2018-12-30 18:00:05 +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
2018-06-23 15:31:40 +08:00
{
2018-12-30 18:00:05 +08:00
this . currentSaveMenu = new NewSaveGameMenu ( ) ;
this . currentSaveMenu . SaveComplete + = this . CurrentSaveMenu_SaveComplete ;
Game1 . activeClickableMenu = this . currentSaveMenu ;
2018-06-23 15:31:40 +08:00
}
2018-12-30 18:00:05 +08:00
2017-07-31 05:42:15 +08:00
2019-01-06 13:31:40 +08:00
// save data to disk
2017-08-06 11:19:38 +08:00
PlayerData data = new PlayerData
{
Time = Game1 . timeOfDay ,
2018-01-11 05:14:19 +08:00
Characters = this . GetPositions ( ) . ToArray ( ) ,
2018-05-01 09:21:31 +08:00
IsCharacterSwimming = Game1 . player . swimming . Value
2017-08-06 11:19:38 +08:00
} ;
2019-01-06 13:31:40 +08:00
this . Helper . Data . WriteJsonFile ( this . RelativeDataPath , data ) ;
2017-08-06 11:19:38 +08:00
2019-01-06 13:31:40 +08:00
// clear any legacy data (no longer needed as backup)
2017-08-06 11:19:38 +08:00
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
2019-01-06 13:31:40 +08:00
PlayerData data = this . Helper . Data . ReadJsonFile < PlayerData > ( this . RelativeDataPath ) ;
2017-08-06 11:19:38 +08:00
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 ;
2018-01-11 05:14:19 +08:00
this . ResumeSwimming ( data ) ;
2017-08-06 11:19:38 +08:00
this . SetPositions ( data . Characters ) ;
this . OnLoaded ? . Invoke ( ) ;
2018-01-11 05:14:19 +08:00
2018-06-23 15:31:40 +08:00
// Notify other mods that load is complete
2018-09-22 01:46:11 +08:00
//AfterLoad.Invoke(this, EventArgs.Empty);
2017-07-31 09:58:48 +08:00
}
2017-07-31 05:42:15 +08:00
2018-12-30 18:00:05 +08:00
/// <summary>Checks to see if the player was swimming when the game was saved and if so, resumes the swimming animation.</summary>
2018-01-11 05:14:19 +08:00
public void ResumeSwimming ( PlayerData data )
{
try
{
2018-12-30 18:00:05 +08:00
if ( data . IsCharacterSwimming )
2018-01-11 05:14:19 +08:00
{
Game1 . player . changeIntoSwimsuit ( ) ;
2018-05-01 09:21:31 +08:00
Game1 . player . swimming . Value = true ;
2018-01-11 05:14:19 +08:00
}
}
2018-12-30 18:00:05 +08:00
catch
2018-01-11 05:14:19 +08:00
{
//Here to allow compatability with old save files.
}
}
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 ;
2018-05-01 09:21:31 +08:00
string name = player . Name ;
string map = player . currentLocation . uniqueName . Value ; //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.
2018-12-30 18:00:05 +08:00
if ( string . IsNullOrEmpty ( map ) )
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 ) ;
2018-12-30 18:00:05 +08:00
if ( type = = null | | npc ? . currentLocation = = null )
2017-07-31 09:58:48 +08:00
continue ;
2018-05-01 09:21:31 +08:00
string name = npc . Name ;
string map = npc . currentLocation . Name ;
2017-07-31 09:58:48 +08:00
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
{
2018-05-01 09:21:31 +08:00
CharacterData data = positions . FirstOrDefault ( p = > p . Type = = CharacterType . Player & & p . Name = = Game1 . player . Name ) ;
2017-08-06 11:19:38 +08:00
if ( data ! = null )
2017-07-31 05:42:15 +08:00
{
2018-05-01 09:21:31 +08:00
Game1 . player . previousLocationName = Game1 . player . currentLocation . Name ;
//Game1.player. 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 ;
2018-05-01 09:21:31 +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
2018-05-01 09:21:31 +08:00
CharacterData data = positions . FirstOrDefault ( p = > p . Type = = type & & p . Name = = npc . Name ) ;
2017-08-06 11:19:38 +08:00
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
2018-05-01 09:21:31 +08:00
Game1 . warpCharacter ( npc , data . Map , new Point ( data . X , data . Y ) ) ;
2017-08-06 11:19:38 +08:00
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" ) ) ;
2018-05-01 09:21:31 +08:00
DirectoryInfo playerDir = new DirectoryInfo ( Path . Combine ( dataDir . FullName , Game1 . player . Name ) ) ;
2017-08-06 11:19:38 +08:00
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
}
}
}