using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Xna.Framework;
using Omegasis.SaveAnywhere.Framework.Models;
using StardewModdingAPI;
using StardewValley;
using StardewValley.Characters;
using StardewValley.Monsters;
namespace Omegasis.SaveAnywhere.Framework
{
/// Provides methods for saving and loading game data.
internal class SaveManager
{
/*********
** Properties
*********/
/// Simplifies access to game code.
private readonly IReflectionHelper Reflection;
/// A callback invoked when data is loaded.
private readonly Action OnLoaded;
/// SMAPI's APIs for this mod.
private readonly IModHelper Helper;
/// The full path to the player data file.
private string SavePath => Path.Combine(this.Helper.DirectoryPath, "data", $"{Constants.SaveFolderName}.json");
/// Whether we should save at the next opportunity.
private bool WaitingToSave;
/*********
** Public methods
*********/
/// Construct an instance.
/// SMAPI's APIs for this mod.
/// Simplifies access to game code.
/// A callback invoked when data is loaded.
public SaveManager(IModHelper helper, IReflectionHelper reflection, Action onLoaded)
{
this.Helper = helper;
this.Reflection = reflection;
this.OnLoaded = onLoaded;
}
/// Perform any required update logic.
public void Update()
{
// perform passive save
if (this.WaitingToSave && Game1.activeClickableMenu == null)
{
Game1.activeClickableMenu = new NewSaveGameMenu();
this.WaitingToSave = false;
}
}
/// Clear saved data.
public void ClearData()
{
Directory.Delete(this.SavePath, recursive: true);
this.RemoveLegacyDataForThisPlayer();
}
/// Initiate a game save.
public void BeginSaveData()
{
// 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 NewSaveGameMenu();
// 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();
}
/// Load all game data.
public void LoadData()
{
// get data
PlayerData data = this.Helper.ReadJsonFile(this.SavePath);
if (data == null)
return;
// apply
Game1.timeOfDay = data.Time;
this.SetPositions(data.Characters);
this.OnLoaded?.Invoke();
}
/*********
** Private methods
*********/
/// Get the current character positions.
private IEnumerable GetPositions()
{
// player
{
var player = Game1.player;
string name = player.name;
string map = player.currentLocation.name;
Point tile = player.getTileLocationPoint();
int facingDirection = player.facingDirection;
yield return new CharacterData(CharacterType.Player, name, map, tile, facingDirection);
}
// NPCs (including horse and pets)
foreach (NPC npc in Utility.getAllCharacters())
{
CharacterType? type = this.GetCharacterType(npc);
if (type == null)
continue;
string name = npc.name;
string map = npc.currentLocation.name;
Point tile = npc.getTileLocationPoint();
int facingDirection = npc.facingDirection;
yield return new CharacterData(type.Value, name, map, tile, facingDirection);
}
}
/// Reset characters to their saved state.
/// The positions to set.
/// Returns whether any NPCs changed position.
private void SetPositions(CharacterData[] positions)
{
// 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())
{
// 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)
continue;
// update NPC
Game1.warpCharacter(npc, data.Map, new Point(data.X, data.Y), false, true);
npc.faceDirection(data.FacingDirection);
}
}
/// Get the character type for an NPC.
/// The NPC to check.
private CharacterType? GetCharacterType(NPC npc)
{
if (npc is Monster)
return null;
if (npc is Horse)
return CharacterType.Horse;
if (npc is Pet)
return CharacterType.Pet;
return CharacterType.Villager;
}
/// Remove legacy save data for this player.
private void RemoveLegacyDataForThisPlayer()
{
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);
}
}
}