/// <summary> /// Saves all relevant game data per the scene and specified fullSave value. /// </summary> /// <param name="fullSave">If true, 'last manually saved scene' and player location data are also saved. If false, these are skipped and just the other scene object data is saved.</param> /// <returns>True if operation was a success. False if there was an error. In the case of an error, an exception likely occured, and an exception message will be printed.</returns> private IEnumerator saveDataForCurrentScene(bool fullSave) { float operationStartTime = Time.time; bool result = true; BinaryFormatter formatter = new BinaryFormatter(); FileStream fsCore = null; FileStream fsPlayer = null; FileStream fsInventory = null; FileStream fsLevel = null; FPESceneSaveData sceneData = mySaveLoadLogic.gatherSceneData(); FPEInventorySaveData inventoryData = mySaveLoadLogic.gatherInventorySaveData(); FPEPlayerStateSaveData playerLocationData = mySaveLoadLogic.gatherPlayerData(); FPEInventoryWorldSaveData[] worldInventoryData = mySaveLoadLogic.gatherInventoryInWorld(); FPEPickupWorldSaveData[] worldPickupData = mySaveLoadLogic.gatherPickupsInWorld(); FPETriggerSaveData[] triggerSaveData = mySaveLoadLogic.gatherTriggerData(); FPEActivateSaveData[] activateSaveData = mySaveLoadLogic.gatherActivateTypeData(); FPEAttachedNoteSaveData[] attachedNoteSaveData = mySaveLoadLogic.gatherAttachedNoteTypeData(); FPEAudioDiaryPlayedStateSaveData[] playedDiarySaveData = mySaveLoadLogic.gatherAudioDiaryPlayedStateData(); FPEJournalSaveData[] journalSaveData = mySaveLoadLogic.gatherJournalSaveData(); FPEDoorSaveData[] doorSaveData = mySaveLoadLogic.gatherDoorTypeData(); FPEGenericObjectSaveData[] genericSaveData = mySaveLoadLogic.gatherGenericSaveTypeData(); // // Your additional Custom Save/Load logic for custom save data types goes here // // Try to write the gathered data to applicable save files on disk try { // We only want to save player location data on manual save operation. When "auto-saving" on change of scene via Doorway, we just want to save the non-player level data. if (fullSave) { // We also only want to write to core save file on a manual save operation. Auto-saves on level change do not count as a "full save" fsCore = new FileStream(fullCoreSaveFileFullPath, FileMode.Create); formatter.Serialize(fsCore, sceneData); fsPlayer = new FileStream(fullPlayerLocationDataSaveFileFullPath, FileMode.Create); formatter.Serialize(fsPlayer, playerLocationData); fsInventory = new FileStream(fullInventoryDataSaveFileFullPath, FileMode.Create); formatter.Serialize(fsInventory, inventoryData); } fsLevel = new FileStream(autoSavePath + "/" + levelDataFilePrefix + sceneData.LastSavedSceneIndex + levelDataFilePostfix, FileMode.Create); formatter.Serialize(fsLevel, worldInventoryData); formatter.Serialize(fsLevel, worldPickupData); formatter.Serialize(fsLevel, triggerSaveData); formatter.Serialize(fsLevel, activateSaveData); formatter.Serialize(fsLevel, attachedNoteSaveData); formatter.Serialize(fsLevel, playedDiarySaveData); formatter.Serialize(fsLevel, journalSaveData); formatter.Serialize(fsLevel, doorSaveData); formatter.Serialize(fsLevel, genericSaveData); // If performing a full save, we also want to flush all previous FULL save data from full directory, and replace it will the stuff we just saved to auto if (fullSave) { copyAllLevelDataFromPathToPath(autoSavePath, fullSavePath); } } catch (Exception e) { Debug.LogError("FPESaveLoadManager:: Failed to save game file(s). Did you make a call the SaveGame() before ever calling StartANewGame()? Reason: " + e.Message); result = false; } finally { if (fsCore != null) { fsCore.Close(); } if (fsLevel != null) { fsLevel.Close(); } if (fsPlayer != null) { fsPlayer.Close(); } if (fsInventory != null) { fsInventory.Close(); } } // Last thing is to clear the progress flag to indicate we're done savingInProgress = false; yield return(result); }
/// <summary> /// Gathers data from player's inventory (items, audio diaries, notes) /// </summary> /// <returns>A saveable data package for the inventory at save time</returns> public FPEInventorySaveData gatherInventorySaveData() { #region HELD_OBJECT FPEInteractableBaseScript.eInteractionType heldObjectType = FPEInteractionManagerScript.Instance.getHeldObjectType(); GameObject heldObject = FPEInteractionManagerScript.Instance.getHeldObject(); FPEHeldObjectSaveData heldObjectData = null; if (heldObjectType == FPEInteractableBaseScript.eInteractionType.PICKUP) { string scrubbedName = heldObject.gameObject.name.Split(FPEObjectTypeLookup.PickupPrefabDelimiter)[0]; // Here, we pass in the first eInventoryItems value since we'll be ignoring it anyway, and it's "better" than exposing a "NO TYPE" in the enum list in the Inspector. heldObjectData = new FPEHeldObjectSaveData(true, scrubbedName, FPEInventoryManagerScript.eInventoryItems.APPLE, heldObject.transform.localRotation); } else if (heldObjectType == FPEInteractableBaseScript.eInteractionType.INVENTORY) { heldObjectData = new FPEHeldObjectSaveData(true, "", heldObject.GetComponent <FPEInteractableInventoryItemScript>().InventoryItemType, heldObject.transform.localRotation); } else { heldObjectData = new FPEHeldObjectSaveData(false, "", FPEInventoryManagerScript.eInventoryItems.APPLE, Quaternion.identity); } #endregion FPEInventoryManagerScript invManager = FPEInventoryManagerScript.Instance; #region INVENTORY_ITEMS List <FPEInteractableInventoryItemScript> invItems = invManager.getInventoryItems(); int[] invQty = invManager.getInventoryQuantities(); List <FPEInventoryItemSaveData> invData = new List <FPEInventoryItemSaveData>(); int tempQty = 0; for (int i = 0; i < invItems.Count; i++) { if (invItems[i].Stackable) { tempQty = invQty[(int)invItems[i].InventoryItemType]; for (int q = 0; q < tempQty; q++) { invData.Add(new FPEInventoryItemSaveData(invItems[i].InventoryItemType)); } } else { invData.Add(new FPEInventoryItemSaveData(invItems[i].InventoryItemType)); } } #endregion #region NOTES_AND_AUDIO_DIARIES // Notes FPENoteEntry[] notes = invManager.getNoteDataForSavedGame(); FPENoteSaveData[] noteData = new FPENoteSaveData[notes.Length]; for (int n = 0; n < notes.Length; n++) { noteData[n] = new FPENoteSaveData(notes[n].NoteTitle, notes[n].NoteBody); } // Audio Diaries FPEAudioDiaryEntry[] diaries = invManager.getAudioDiaryDataForSavedGame(); FPEAudioDiaryEntrySaveData[] diaryData = new FPEAudioDiaryEntrySaveData[diaries.Length]; for (int a = 0; a < diaries.Length; a++) { diaryData[a] = new FPEAudioDiaryEntrySaveData(diaries[a].DiaryTitle, diaries[a].getAudioDiaryClipPath(), diaries[a].ShowDiaryTitle); } #endregion FPEInventorySaveData inventoryData = new FPEInventorySaveData(heldObjectData, invData.ToArray(), diaryData, noteData); return(inventoryData); }
/// <summary> /// Restores saved level data to the currently loaded scene, if it exists. /// </summary> /// <param name="fullLoad">If true, player location and inventory data will be loaded, and player will be relocated in world space per that data.</param> /// <returns>True if operation was a success. False if there was an error. In the case of an error, an exception likely occured, and an exception message will be printed.</returns> private IEnumerator restoreDataForCurrentScene(bool fullLoad) { bool result = true; // If doing a full load, we want to copy all 'full' save level data files into the 'auto' directory, then restore from there. if (fullLoad) { copyAllLevelDataFromPathToPath(fullSavePath, autoSavePath); } string levelDataFilename = autoSavePath + "/" + levelDataFilePrefix + SceneManager.GetActiveScene().buildIndex + levelDataFilePostfix; if (File.Exists(levelDataFilename)) { FPEPlayerStateSaveData loadedPlayerLocationData = null; FPEInventorySaveData loadedInventoryData = null; FPEInventoryWorldSaveData[] loadedWorldInventoryData = null; FPEPickupWorldSaveData[] loadedWorldPickupData = null; FPETriggerSaveData[] loadedTriggerData = null; FPEActivateSaveData[] loadedActivateData = null; FPEAttachedNoteSaveData[] loadedAttachedNoteData = null; FPEAudioDiaryPlayedStateSaveData[] loadedAudioDiaryPlayedData = null; FPEJournalSaveData[] loadedJournalData = null; FPEDoorSaveData[] loadedDoorData = null; FPEGenericObjectSaveData[] loadedGenericData = null; BinaryFormatter formatter = new BinaryFormatter(); FileStream fsPlayer = null; FileStream fsInventory = null; FileStream fsLevel = null; // Try to actually read in the data from disk try { fsLevel = new FileStream(levelDataFilename, FileMode.Open); // Note: Read the data in from file in the same order it was written to file loadedWorldInventoryData = (FPEInventoryWorldSaveData[])formatter.Deserialize(fsLevel); loadedWorldPickupData = (FPEPickupWorldSaveData[])formatter.Deserialize(fsLevel); loadedTriggerData = (FPETriggerSaveData[])formatter.Deserialize(fsLevel); loadedActivateData = (FPEActivateSaveData[])formatter.Deserialize(fsLevel); loadedAttachedNoteData = (FPEAttachedNoteSaveData[])formatter.Deserialize(fsLevel); loadedAudioDiaryPlayedData = (FPEAudioDiaryPlayedStateSaveData[])formatter.Deserialize(fsLevel); loadedJournalData = (FPEJournalSaveData[])formatter.Deserialize(fsLevel); loadedDoorData = (FPEDoorSaveData[])formatter.Deserialize(fsLevel); loadedGenericData = (FPEGenericObjectSaveData[])formatter.Deserialize(fsLevel); // // Your additional Custom Save/Load logic for custom save data types goes here // // Triggers and Activate types mySaveLoadLogic.restoreTriggerData(loadedTriggerData); mySaveLoadLogic.restoreActivateData(loadedActivateData); // Doors mySaveLoadLogic.restoreDoorData(loadedDoorData); // Generic Saveable Objects mySaveLoadLogic.restoreGenericSaveTypeData(loadedGenericData); // Inventory type objects in the world mySaveLoadLogic.removeAllInventoryInWorld(fullLoad); mySaveLoadLogic.createWorldInventory(loadedWorldInventoryData); // Pickup type objects in the world mySaveLoadLogic.removeAllPickupsInWorld(fullLoad); mySaveLoadLogic.createWorldPickups(loadedWorldPickupData); // Attached Notes, Diaries, and Journals // Note: We restore these last, because they can be attached to Inventory Items and Pickups or other interactables. If for example, we loaded these BEFORE restoring pickups, // the associated pickups would get their attached note/diary data restored, then be deleted and replaced with a fresh copy of the prefab, thus erasing the restored note/diary // state. This way, we ensure the 'final' restored prefab of a pickup/inventory item is in place and loaded before we restore its attached note or diary status. mySaveLoadLogic.restoreAttachedNoteData(loadedAttachedNoteData); mySaveLoadLogic.restoreAudioDiaryPlaybackStateData(loadedAudioDiaryPlayedData); mySaveLoadLogic.restoreJournalData(loadedJournalData); // Only restore player location and move them if required. We do this last because the functions above may destroy held object, depending on fullLoad value. if (fullLoad) { fsPlayer = new FileStream(fullPlayerLocationDataSaveFileFullPath, FileMode.Open); loadedPlayerLocationData = (FPEPlayerStateSaveData)formatter.Deserialize(fsPlayer); mySaveLoadLogic.relocatePlayer(loadedPlayerLocationData); fsInventory = new FileStream(fullInventoryDataSaveFileFullPath, FileMode.Open); loadedInventoryData = (FPEInventorySaveData)formatter.Deserialize(fsInventory); mySaveLoadLogic.restoreInventorySaveData(loadedInventoryData); } } catch (Exception e) { Debug.LogError("FPESaveLoadManager:: Failed to load game file. Reason: " + e.Message); result = false; } finally { if (fsLevel != null) { fsLevel.Close(); } if (fsPlayer != null) { fsPlayer.Close(); } if (fsInventory != null) { fsInventory.Close(); } } } // Last thing is to clear the progress flag to indicate we're done restoringDataInProgress = false; yield return(result); }
/// <summary> /// Restores player's inventory (items, audio diaries, notes) /// </summary> /// <param name="data">The data from which to base restored inventory</param> public void restoreInventorySaveData(FPEInventorySaveData data) { FPEInventoryManagerScript invManager = FPEInventoryManagerScript.Instance; // Clear existing inventory invManager.clearInventoryItems(); // Held Object FPEHeldObjectSaveData heldObjData = data.HeldObjectData; #region HELD_OBJECT if (heldObjData.HeldSomething) { GameObject tempLoadedObject = null; // Pickup Type if (heldObjData.PickupPrefabName != "") { Object tempObject = Resources.Load(FPEObjectTypeLookup.PickupResourcePath + heldObjData.PickupPrefabName); if (tempObject != null) { tempLoadedObject = Instantiate(tempObject) as GameObject; tempLoadedObject.name = heldObjData.PickupPrefabName; tempLoadedObject.transform.localRotation = heldObjData.LocalRotation; } else { Debug.LogError("FPESaveLoadLogic:: Loading data encountered unknown Pickup named '" + heldObjData.PickupPrefabName + "'. No prefab was found with this name. This object will NOT be loaded. Ensure that all Pickup prefabs are located in the '" + FPEObjectTypeLookup.PickupResourcePath + "' sub folder of a Resources folder."); } } // Inventory Type else { if (inventoryLookupTable.ContainsKey(heldObjData.InventoryItemType)) { string tempPath = ""; if (inventoryLookupTable.TryGetValue(heldObjData.InventoryItemType, out tempPath)) { tempLoadedObject = Instantiate(Resources.Load(FPEObjectTypeLookup.InventoryResourcePath + tempPath)) as GameObject; tempLoadedObject.transform.localRotation = heldObjData.LocalRotation; } else { Debug.LogError("FPESaveLoadLogic:: Loading data could not get value for InventoryItemType '" + heldObjData.InventoryItemType + "'"); } } else { Debug.LogError("FPESaveLoadLogic:: Loading data encountered unknown InventoryItemType '" + heldObjData.InventoryItemType + "'. This object will NOT be loaded. Ensure that all Inventory Item prefabs are located in the '" + FPEObjectTypeLookup.InventoryResourcePath + "' sub folder of a Resources folder. Also ensure that there is an entry in the FPEObjectTypeLookup 'inventoryItemsLookup' Dictionary for type '" + heldObjData.InventoryItemType + "'"); } } // Lastly, put the object into player's hand FPEInteractablePickupScript pickup = tempLoadedObject.GetComponent <FPEInteractablePickupScript>(); if (pickup) { FPEInteractionManagerScript.Instance.holdObjectFromGameLoad(pickup); } else { Debug.LogError("FPESaveLoadLogic:: Loading held object for object '" + tempLoadedObject.gameObject.name + "', but its prefab had no attached FPEInteractablePickupScript component. Object will not be loaded. Check prefab."); } } #endregion #region INVENTORY_ITEMS FPEInventoryItemSaveData[] loadedItemData = data.InventoryItemData; // Create all the items, and add each to inventory GameObject tempInvObject = null; string tempInvPath; FPEInteractableInventoryItemScript tempInvItem = null; // We start at index 1 to skip over our padded 0th value for (int i = 0; i < loadedItemData.Length; i++) { if (inventoryLookupTable.ContainsKey(loadedItemData[i].InventoryItemType)) { // Added this as an if, did that break things? if (inventoryLookupTable.TryGetValue(loadedItemData[i].InventoryItemType, out tempInvPath)) { tempInvObject = Instantiate(Resources.Load(FPEObjectTypeLookup.InventoryResourcePath + tempInvPath)) as GameObject; tempInvItem = tempInvObject.GetComponent <FPEInteractableInventoryItemScript>(); if (tempInvItem != null) { FPEInteractionManagerScript.Instance.putObjectIntoInventory(tempInvItem, false); } else { Debug.LogError("FPESaveLoadLogic:: Loaded Inventory Item prefab '" + FPEObjectTypeLookup.InventoryResourcePath + tempInvPath + "' had no attached FPEInteractableInventoryItemScript component. Item will NOT be restored into player inventory"); } } else { Debug.LogError("FPESaveLoadLogic:: Loading data could not get value for InventoryItemType '" + loadedItemData[i].InventoryItemType + "'"); } } else { Debug.LogError("FPESaveLoadLogic:: Loading data encountered unknown InventoryItemType '" + loadedItemData[i].InventoryItemType + "'. This object will NOT be restored into player inventory. Ensure that there is an entry in the FPEObjectTypeLookup 'inventoryItemsLookup' Dictionary for type '" + loadedItemData[i].InventoryItemType + "'"); } } #endregion #region NOTES_AND_AUDIO_DIARIES FPENoteSaveData[] loadedNoteData = data.NoteData; List <FPENoteEntry> noteEntries = new List <FPENoteEntry>(); for (int n = 0; n < loadedNoteData.Length; n++) { noteEntries.Add(loadedNoteData[n].getNoteEntry()); } invManager.setNoteDataFromSavedGame(noteEntries); FPEAudioDiaryEntrySaveData[] loadedDiaryData = data.AudioDiaryData; List <FPEAudioDiaryEntry> diaryEntries = new List <FPEAudioDiaryEntry>(); for (int a = 0; a < loadedDiaryData.Length; a++) { diaryEntries.Add(loadedDiaryData[a].getAudioDiaryEntry()); } invManager.setAudioDiaryDataFromSavedGame(diaryEntries); #endregion }