Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using Assets.Scripts.Data;
- using UnityEngine;
- public class DataManager : Singleton<DataManager>
- {
- public enum EState
- {
- Idle,
- Saving,
- Loading
- }
- public event Action SaveStartedEventHandler;
- public event Action SaveEndedEventHandler;
- const string LevelSaveFileName = "level.json";
- const string BackupFolder = "backups";
- const int BackupCount = 3;
- [SerializeField] float _autoSavePeriod = 60f;
- float _lastAutoSaveTime;
- bool _saveScheduled;
- EState _state = EState.Idle;
- Queue<Action> _callbackQueue = new Queue<Action>();
- public bool CanSave
- {
- get
- {
- return (GameManager.Instance.State == GameManager.EState.Playing ||
- GameManager.Instance.State == GameManager.EState.Paused)
- // game manager is in the playing or paused state
- && GuiStateMachine.Instance.CurrentState == GuiStateMachine.State.Normal
- // gui is in the normal state
- && CameraController.Instance.State == CameraController.EState.Playing;
- // camera controller is in the playing state
- }
- }
- public string BackupFolderPath
- {
- get { return Application.persistentDataPath + "/" + BackupFolder; }
- }
- public string SaveFilePath
- {
- get { return Application.persistentDataPath + "/" + LevelSaveFileName; }
- }
- void Start()
- {
- // register to events that cause autosave to be triggered.
- // NOTE: This may incur a memory leak (should really do some investigation on this)
- PlacableSystem.ItemPlacedEventHandler += _ => _saveScheduled = true;
- InventorySystem.ItemPickedUpEventHandler += _ => _saveScheduled = true;
- InventorySystem.ItemDroppedEventHandler += _ => _saveScheduled = true;
- DoorSystem.Instance.DoorOperationOccurredEventHandler += ( _, __ ) => _saveScheduled = true;
- UsableSystem.UsableUsedEventHandler += _ => _saveScheduled = true;
- LevelManager.LevelLoadedEventHandler += OnLevelLoaded;
- }
- void OnLevelLoaded( string levelName )
- {
- AsgardEventSystem.EventInvocationEndedEventHandler += _ => _saveScheduled = true;
- }
- void EnsureBackupFolderExists()
- {
- if ( !Directory.Exists( BackupFolderPath ) )
- {
- Directory.CreateDirectory( BackupFolderPath );
- }
- }
- void SaveLevelFile( LevelSaveData levelSaveData )
- {
- var json = levelSaveData.SerializeToJson();
- // If it already exists then back it up
- BackupCurrentSave();
- // write the save data to the file.
- File.WriteAllText( SaveFilePath, json );
- Debug.Log( string.Format( "Saved level state to {0}", SaveFilePath ) );
- _state = EState.Idle;
- Debug.Log( "Save ended" );
- if ( SaveEndedEventHandler != null )
- {
- SaveEndedEventHandler();
- }
- }
- LevelSaveData LoadLevelFile()
- {
- if ( !DoesLevelSaveFileExists() )
- {
- Debug.LogError( "Tried to load save file when the file does not exist" );
- return null;
- }
- var json = GetLevelSaveFileContent();
- return LevelSaveData.DeserializeFromJson( json );
- }
- void BackupCurrentSave()
- {
- Debug.Log( "Started backup..." );
- // check to see if a save currently exists
- if ( !DoesLevelSaveFileExists() )
- {
- Debug.Log( "No save file exists. No backup created." );
- return;
- }
- // ensure the backup folder exists
- EnsureBackupFolderExists();
- // delete the oldest backups until the backup count is equal to the backup limit constant - 1
- var backups = Directory.GetFiles( BackupFolderPath );
- var orderedBackups = backups.OrderBy( File.GetCreationTime ).ToList();
- while ( orderedBackups.Count >= BackupCount )
- {
- Debug.Log( string.Format( "Backup count is >= {0}. Deleting backup '{1}'", orderedBackups.Count,
- orderedBackups[ 0 ] ) );
- File.Delete( orderedBackups[ 0 ] );
- orderedBackups.RemoveAt( 0 );
- }
- // copy the current save file to the backup folder
- var fileName = BackupFolderPath + "/level_backup_" + DateTime.Now.Ticks;
- File.WriteAllText( fileName , File.ReadAllText( SaveFilePath ) );
- Debug.Log( "Created new backup with name " + fileName + ". Backup complete.");
- }
- [ContextMenu( "Load Level State" )]
- public void LoadSaveFile()
- {
- if ( _state != EState.Idle )
- {
- // do not perform loads during non-idle states
- Debug.LogError( string.Format( "Tried to perform a load operation when in the {0} state", _state ) );
- return;
- }
- _state = EState.Loading;
- Debug.Log( "Load occurring" );
- LoadSaveFile( LoadLevelFile() );
- }
- void LoadSaveFile( LevelSaveData levelSaveData )
- {
- // define what should happen once the level is loaded.
- Action setupLevelStateCallback = () =>
- {
- // restore event states (this must be done first so the placable system doesn't invoke events)
- foreach ( var savedEventState in levelSaveData.Events )
- {
- var asgardEvent = AsgardEventSystem.Instance.GetAsgardEvent( savedEventState.EventName );
- if ( asgardEvent == null )
- {
- Debug.LogError(
- string.Format(
- "Tried to restore event state for event with name '{0}' but it was not returned by the event system query.",
- savedEventState.EventName ) );
- continue;
- }
- // restore the "has been invoked" state.
- asgardEvent.HasBeenInvoked = savedEventState.HasBeenInvoked;
- }
- // restore all door states
- foreach ( var doorData in levelSaveData.Doors )
- {
- if ( doorData.IsInOpenState )
- {
- DoorSystem.Instance.DoDoorOperationInstantly( doorData.UniqueId, DoorSystem.EDoorOperaton.Open );
- }
- else
- {
- DoorSystem.Instance.DoDoorOperationInstantly( doorData.UniqueId, DoorSystem.EDoorOperaton.Close );
- }
- }
- //TODO: Add saving of usables back in if needed.
- //// restore all the usable states
- //foreach ( var usableData in levelSaveData.Usables )
- //{
- // var usable = UsableSystem.Instance.FindUsable( usableData.UniqueId );
- // if ( usable == null )
- // {
- // Debug.LogError( string.Format( "Tried to restore saved data of usable with id {0} but it could not be found!", usableData.UniqueId ) );
- // continue;
- // }
- // UsableSystem.Instance.SetupUsable( usable, usableData.IsUsed );
- //}
- // delete all items in the scene
- var worldItems = WorldItemSystem.Instance.Components;
- foreach ( var worldItem in worldItems )
- {
- WorldItemSystem.Instance.DestroyWorldItem( worldItem );
- }
- // place items from save file in the scene, ensuring to set placed state and z
- foreach ( var savedItem in levelSaveData.Items )
- {
- var isPlaced = !string.IsNullOrEmpty( savedItem.IsPlaced );
- // if the item is placed, then call the placable system api otherwise just place the item through the world item system
- if ( isPlaced )
- {
- PlacableSystem.Instance.PlaceItem( savedItem.IsPlaced, savedItem.Name );
- }
- else
- {
- WorldItemSystem.Instance.InstantiateOnItemLayer( savedItem.Name, savedItem.Position );
- }
- }
- // clear player inventory objects
- InventorySystem.Instance.RemoveAllItemsFromInventory();
- // restore player inventory objects
- foreach ( var inventorySlotData in levelSaveData.InventorySlots )
- {
- InventorySystem.Instance.AddItemToInventory( inventorySlotData.ItemName, inventorySlotData.Index );
- }
- // restore player character position
- CharacterController.Instance.SetPosition( levelSaveData.Character.Position );
- CharacterController.Instance.SetRotation( levelSaveData.Character.Rotation );
- // snap camera position to player character (this might be a bad idea as i'm taking over control...)
- CameraController.Instance.SnapToTarget();
- Debug.Log( "Loaded level state from " + Application.dataPath + "/" + LevelSaveFileName );
- Debug.Log( "Load ended" );
- _state = EState.Idle;
- };
- // load the relevant level
- if ( LevelManager.Instance.CurrentSceneName != levelSaveData.LevelName )
- {
- // if the level is not loaded, we want to load it and execute setup of the level as the callback
- GameManager.Instance.LoadLevel( levelSaveData.LevelName, setupLevelStateCallback );
- }
- else
- {
- // otherwise just call the callback right now
- setupLevelStateCallback();
- }
- }
- [ContextMenu( "Save Level State" )]
- void SaveLevelState()
- {
- if ( _state != EState.Idle )
- {
- // don't peform saves during non-idle states
- return;
- }
- Debug.Log( "Save occurring" );
- _state = EState.Saving;
- if ( SaveStartedEventHandler != null )
- {
- SaveStartedEventHandler();
- }
- var levelSaveData = new LevelSaveData();
- Debug.Log( "saving " + WorldItemSystem.Instance.Components.Count );
- // add all items in the world, not forgetting to determine their placed state
- foreach ( var worldItem in WorldItemSystem.Instance.Components )
- {
- // skip items that have been marked for destruction.
- if ( worldItem.IsMarkedForDestruction )
- {
- continue;
- }
- var placed = PlacableSystem.Instance.GetPlacableItemIsPlacedOn( worldItem );
- var uniquePlacableId = placed != null ? placed.PlacableUniqueName : string.Empty;
- levelSaveData.Items.Add( new ItemData
- {
- Name = worldItem.Model.Name,
- Position = worldItem.Transform.position,
- IsPlaced = uniquePlacableId
- } );
- }
- // add player character position
- levelSaveData.Character = new CharacterData
- {
- Position = CharacterController.Instance.Position,
- Rotation = CharacterController.Instance.Rotation
- };
- // add player inventory
- for ( var i = 0; i < InventorySystem.Instance.PlayerInventorySize; i++ )
- {
- var inventorySlot = InventorySystem.Instance.GetInventorySlotAtIndex( i );
- if ( inventorySlot.Model.IsSet )
- {
- levelSaveData.InventorySlots.Add( new InventorySlotData
- {
- Index = i,
- ItemName = inventorySlot.Model.Name
- } );
- }
- }
- // add event states
- foreach ( var asgardEvent in AsgardEventSystem.Instance.AsgardEvents )
- {
- // get the name which should be unique (as we use this to restore the state back into the array on load)
- var eventName = asgardEvent.EventName;
- // get the invoked state
- var hasBeenInvoked = asgardEvent.HasBeenInvoked;
- levelSaveData.Events.Add( new EventData
- {
- EventName = eventName,
- HasBeenInvoked = hasBeenInvoked
- } );
- }
- // add level name
- levelSaveData.LevelName = LevelManager.Instance.CurrentSceneName;
- // add door states
- foreach ( var door in DoorSystem.Instance.Components )
- {
- var isInOpenState = door.IsInOpenState;
- var uniqueId = door.UniqueId;
- levelSaveData.Doors.Add( new DoorData
- {
- IsInOpenState = isInOpenState,
- UniqueId = uniqueId
- } );
- }
- // add the usable's states
- foreach ( var usable in UsableSystem.Instance.Components )
- {
- var uniqueId = usable.UniqueId;
- var isUsed = usable.IsUsed;
- levelSaveData.Usables.Add( new UsableData
- {
- IsUsed = isUsed,
- UniqueId = uniqueId
- } );
- }
- // save file
- SaveLevelFile( levelSaveData );
- // if any interested objects want a callback on save, we do it now after everything is complete. This is used for quitting the game.
- ExecuteCallbacks();
- }
- public bool DoesLevelSaveFileExists()
- {
- return File.Exists( SaveFilePath );
- }
- string GetLevelSaveFileContent()
- {
- return File.ReadAllText( SaveFilePath );
- }
- void LateUpdate()
- {
- // check to see if we should autosave.
- if ( Time.time >= _lastAutoSaveTime + _autoSavePeriod || _saveScheduled )
- {
- // we need to be in the appropriate state
- if ( CanSave )
- {
- // save the game state and reset the timer.
- _lastAutoSaveTime = Time.time;
- _saveScheduled = false;
- SaveLevelState();
- Debug.Log( "Autosave occurred at " + DateTime.Now );
- }
- }
- }
- public void ScheduleSave( Action callback = null )
- {
- if ( callback != null )
- {
- _callbackQueue.Enqueue( callback );
- }
- _saveScheduled = true;
- }
- void ExecuteCallbacks()
- {
- while ( _callbackQueue.Count > 0 )
- {
- var callback = _callbackQueue.Dequeue();
- callback();
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement