using System; using System.Collections.Generic; using UnityEngine; using Framework.Services; using Framework.Storage.Containers; namespace Framework.Storage { /// Manages all persistent data of the game [ ServiceScope(Scope.Session) ] public class PersistentGameStateManager : Service, IPersistentGameStateStore { /// Filename for the saved game state file in each save slot folder public static readonly string GameStateFilename = "GameState.xml"; /// Called when the persistent state store has been initialized public event Action Initialized { add { if(IsInitialized) { value(); } else { if(this.initializedEventSubscribers == null) { this.initializedEventSubscribers = new List(); this.initializedEventSubscribers.Add(value); } else if(!this.initializedEventSubscribers.Contains(value)) { this.initializedEventSubscribers.Add(value); } } } remove { this.initializedEventSubscribers.Remove(value); } } /// Initialized a new persistent game state manager public PersistentGameStateManager() { this.activeSaveSlotIndex = -1; } /// Injects the instance's dependencies /// Save slot manager used to look up save slot paths /// /// This method is called automatically by the services framework. Any parameters /// it requires will be filled out by looking up the respective services and creating /// them as needed. /// protected void Inject(ISaveSlotManager saveSlotManager) { this.saveSlotManager = saveSlotManager; } /// Initializes the types that will be used by the persistent data store /// /// Type that holds the current state of the game as it is stored in a save slot /// /// /// The default implementation of the persistent store requires this method to be called /// before any of the other properties and methods of the persistent store will work. /// It will load the game's current settings from their default locations if available /// and create new default instances of no saved settings exist. /// public void Initialize( IPersistentStateSerializer gameStateSerializer = null ) where TGameState : class, IGameState, new() { if(IsInitialized) { Debug.LogError("Persistent game state manager was initialized a second time."); return; } { var gameStateContainer = new XmlBackedContainer( StandardDirectories.SaveGamePath, GameStateFilename ); gameStateContainer.CustomSerializer = gameStateSerializer; this.gameState = gameStateContainer; this.changeStateDirectoryDelegate = new Action( gameStateContainer.ChangeDirectory ); } Debug.Log("Persistent game state manager initialized"); OnInitialized(); } /// Whether the persistent data store has been initialized already public bool IsInitialized { get { return (this.gameState != null); } } /// Container for the current state of the game public IVerifiedPersistentContainer GameState { get { if(!IsInitialized) { throw new InvalidOperationException( "Can't access game state; persistent game state manager not initialized uet" ); } if(this.activeSaveSlotIndex == -1) { throw new InvalidOperationException( "Can't access game state; no active save slot yet" ); } return this.gameState; } } /// Save slot that is currently being played, -1 if none public int ActiveSaveSlot { get { return this.activeSaveSlotIndex; } } /// Starts a new game state in the specified slot /// Index of the slot a new game will be started in public void StartNewGame(int slotIndex) { if(!IsInitialized) { throw new InvalidOperationException( "Can't start new game; persistent game state manager not initialized uet" ); } if(this.saveSlotManager == null) { throw new InvalidOperationException( "Can't start new game; save slot manager has not been created yet" ); } this.activeSaveSlotIndex = slotIndex; //this.saveSlotManager.DeleteSaveSlot(slotIndex); this.changeStateDirectoryDelegate(this.saveSlotManager.GetSaveSlotPath(slotIndex)); this.gameState.Reset(); Debug.Log( "New game started in slot " + slotIndex.ToString() + " (home in '" + this.saveSlotManager.GetSaveSlotPath(slotIndex) + "')" ); } /// Loads the game state from the specified slot /// Index of hte slot from which the game will be loaded public void LoadGame(int slotIndex) { if(!IsInitialized) { throw new InvalidOperationException( "Can't load game; persistent game state manager not initialized yet" ); } if(this.saveSlotManager == null) { throw new InvalidOperationException( "Can't load game; save slot manager has not been created yet" ); } this.activeSaveSlotIndex = slotIndex; this.changeStateDirectoryDelegate(this.saveSlotManager.GetSaveSlotPath(slotIndex)); this.gameState.Load(); if(this.gameState.IsGenuine) { Debug.Log("Game loaded from slot " + slotIndex.ToString() + " (game state is genuine)"); } else { Debug.Log("Game loaded from slot " + slotIndex.ToString() + " (tampering was detected)"); } } /// Saves the game in the active slot public void SaveGame() { if(this.activeSaveSlotIndex == -1) { throw new InvalidOperationException("Can't save game; no save slot active yet"); } this.gameState.Save(); Debug.Log("Game saved to slot " + this.activeSaveSlotIndex.ToString()); } /// Deletes the game state in the specified slot /// Index of the slot in which the game will be deleted /// /// True if a saved state existed in the specified slot and was deleted, false otherwise /// public bool DeleteGame(int slotIndex) { if(this.saveSlotManager == null) { throw new InvalidOperationException( "Can't delete game; save slot manager has not been created yet" ); } Debug.Log("Deleting saved game in slot " + slotIndex.ToString()); if(this.saveSlotManager.IsSlotFilled(slotIndex)) { this.saveSlotManager.DeleteSaveSlot(slotIndex); return true; } else { return false; } } /// Whether a saved game exists in the specified slot /// Index of the slot that will be checked /// True if a saved game exists in the specified slot public bool HasSavedGame(int slotIndex) { return this.saveSlotManager.IsSlotFilled(slotIndex); } /// Fires the OnInitialized() event /// > /// We auto-clear the subscribers after firing since the event is supposed to /// only happen once per game and we don't want to keep a bunch of objects hanging /// by their subscription to the event list. /// protected virtual void OnInitialized() { if(this.initializedEventSubscribers != null) { IList subscribers = this.initializedEventSubscribers; this.initializedEventSubscribers = null; int subscriberCount = subscribers.Count; for(int index = 0; index < subscriberCount; ++index) { subscribers[index](); } } } /// Container for the current state of the game private IVerifiedPersistentContainer gameState; /// List of subscribers for the event private IList initializedEventSubscribers; /// Index of the active save slot private int activeSaveSlotIndex; /// Delegate for the XmlBacked container's ChangeDirectory() method private Action changeStateDirectoryDelegate; /// Manages the save slots into which game states can be saved private ISaveSlotManager saveSlotManager; } } // namespace Framework.Storage