// Copyright (c) Pixel Crushers. All rights reserved. using UnityEngine; using System.Collections.Generic; using System.IO; namespace PixelCrushers.QuestMachine { /// /// Utility to serialize the minimum data necessary for design-time quests. Only saves: /// - Quest giver ID /// - Static tags /// - Times accepted /// - Time last accepted /// - Cooldown time remaining /// - Show in HUD /// - Quest state /// - Quest node states [skipped if inactive] /// - Counter values [skipped if inactive] /// - True condition count on all condition sets [skipped if inactive] /// - Quest indicator states [skipped if inactive] /// public static class QuestStateSerializer { /// /// - Version 2: Added quest condition "alreadyTrue" values. /// - Version 1: Use for compatibility with saves made in QM version 1.2.29 or earlier. /// public static int version = 2; /// /// Returns minimum save data for a design-time quest. /// /// The quest to serialize. /// A byte array containing states and counter values. public static byte[] Serialize(Quest quest) { if (quest == null) return null; using (var memoryStream = new MemoryStream()) { using (var binaryWriter = new BinaryWriter(memoryStream)) { WriteQuestDataToStream(binaryWriter, quest); } return memoryStream.ToArray(); } } /// /// Copies data from a byte array into an existing design-time quest. /// /// The quest to receive the data. /// A byte array generated by the Serialize method. public static void DeserializeInto(Quest quest, byte[] bytes, bool allowThrowExceptions = false) { if (quest == null || bytes == null) return; using (var memoryStream = new MemoryStream(bytes)) { using (var binaryReader = new BinaryReader(memoryStream)) { try { ReadQuestDataFromStream(binaryReader, quest); } catch (System.Exception e) { if (allowThrowExceptions) throw e; } } } } //------------------------------------------------------------------ // Save: private static void WriteQuestDataToStream(BinaryWriter binaryWriter, Quest quest) { if (quest == null) return; var state = quest.GetState(); binaryWriter.Write((byte)state); WriteTagDictionaryToStream(binaryWriter, quest.tagDictionary, quest.name); binaryWriter.Write(StringField.GetStringValue(quest.questGiverID)); binaryWriter.Write(quest.timesAccepted); quest.UpdateCooldown(); binaryWriter.Write((double)quest.cooldownSecondsRemaining); binaryWriter.Write(quest.showInTrackHUD); WriteConditionSetDataToStream(binaryWriter, quest.autostartConditionSet); WriteConditionSetDataToStream(binaryWriter, quest.offerConditionSet); // Don't save the info below if waiting to start: if (state == QuestState.WaitingToStart && !quest.saveAllIfWaitingToStart) return; for (int i = 0; i < quest.counterList.Count; i++) { binaryWriter.Write(quest.counterList[i].currentValue); } for (int i = 0; i < quest.nodeList.Count; i++) { WriteQuestNodeDataToStream(binaryWriter, quest.nodeList[i]); } WriteQuestIndicatorsToStream(binaryWriter, quest.indicatorStates); } private static void WriteQuestNodeDataToStream(BinaryWriter binaryWriter, QuestNode node) { if (node == null) return; var state = node.GetState(); binaryWriter.Write((byte)state); WriteConditionSetDataToStream(binaryWriter, node.conditionSet); WriteTagDictionaryToStream(binaryWriter, node.tagDictionary, StringField.GetStringValue(node.internalName)); } private static void WriteConditionSetDataToStream(BinaryWriter binaryWriter, QuestConditionSet conditionSet) { if (conditionSet == null) return; binaryWriter.Write((byte)conditionSet.numTrueConditions); if (version >= 2) { for (int i = 0; i < conditionSet.conditionList.Count; i++) { binaryWriter.Write(conditionSet.conditionList[i].alreadyTrue); } } } private static void WriteTagDictionaryToStream(BinaryWriter binaryWriter, TagDictionary tags, string questOrNodeName) { if (tags == null) return; binaryWriter.Write(tags.dict.Count); foreach (var kvp in tags.dict) { if (string.IsNullOrEmpty(kvp.Key)) { if (Debug.isDebugBuild) Debug.LogWarning("Quest Machine: While serializing quest tags, found a tag with a blank name in " + questOrNodeName + "."); } else { binaryWriter.Write(kvp.Key); binaryWriter.Write(kvp.Value); } } } private static void WriteQuestIndicatorsToStream(BinaryWriter binaryWriter, Dictionary indicatorRecords) { if (indicatorRecords == null) return; binaryWriter.Write(indicatorRecords.Count); foreach (var kvp in indicatorRecords) { binaryWriter.Write(kvp.Key); binaryWriter.Write((int)kvp.Value); } } //------------------------------------------------------------------ // Load: private static void ReadQuestDataFromStream(BinaryReader binaryReader, Quest quest) { if (quest == null) return; var state = (QuestState)binaryReader.ReadByte(); ReadTagDictionaryFromStream(binaryReader, quest.tagDictionary, quest.name); quest.questGiverID.value = binaryReader.ReadString(); quest.timesAccepted = binaryReader.ReadInt32(); quest.cooldownSecondsRemaining = (float)binaryReader.ReadDouble(); quest.showInTrackHUD = binaryReader.ReadBoolean(); ReadConditionSetDataFromStream(binaryReader, quest.autostartConditionSet); ReadConditionSetDataFromStream(binaryReader, quest.offerConditionSet); if (state == QuestState.WaitingToStart && !quest.saveAllIfWaitingToStart) { quest.SetState(state, false); return; } // Don't load the info below if waiting to start: for (int i = 0; i < quest.counterList.Count; i++) { quest.counterList[i].SetValue(binaryReader.ReadInt32(), QuestCounterSetValueMode.DontInformListeners); } for (int i = 0; i < quest.nodeList.Count; i++) { ReadQuestNodeDataFromStream(binaryReader, quest.nodeList[i]); } ReadQuestIndicatorsFromStream(binaryReader, quest.indicatorStates); quest.SetRuntimeReferences(); quest.SetState(state, false); QuestMachineMessages.QuestStateChanged(quest, quest.id, quest.GetState()); } private static void ReadQuestNodeDataFromStream(BinaryReader binaryReader, QuestNode node) { node.SetState((QuestNodeState)binaryReader.ReadByte(), false); ReadConditionSetDataFromStream(binaryReader, node.conditionSet); ReadTagDictionaryFromStream(binaryReader, node.tagDictionary, StringField.GetStringValue(node.internalName)); } private static void ReadConditionSetDataFromStream(BinaryReader binaryReader, QuestConditionSet conditionSet) { conditionSet.numTrueConditions = binaryReader.ReadByte(); if (version >= 2) { for (int i = 0; i < conditionSet.conditionList.Count; i++) { conditionSet.conditionList[i].alreadyTrue = binaryReader.ReadBoolean(); } } } private static void ReadTagDictionaryFromStream(BinaryReader binaryReader, TagDictionary tags, string questOrNodeName) { if (tags == null) return; tags.dict.Clear(); var count = binaryReader.ReadInt32(); for (int i = 0; i < count; i++) { var key = binaryReader.ReadString(); var value = binaryReader.ReadString(); if (string.IsNullOrEmpty(key)) { //---Suppress warning: if (Debug.isDebugBuild) Debug.LogWarning("Quest Machine: While deserializing quest tags, found a tag with a blank name in " + questOrNodeName + "."); } else if (tags.dict.ContainsKey(key)) { //---Suppress warning: if (Debug.isDebugBuild) Debug.LogWarning("Quest Machine: While deserializing quest tags, found two tags with the name '" + key + "' in " + questOrNodeName + "."); } else { tags.dict.Add(key, value); } } } private static void ReadQuestIndicatorsFromStream(BinaryReader binaryReader, Dictionary indicatorRecords) { if (indicatorRecords == null) return; indicatorRecords.Clear(); var count = binaryReader.ReadInt32(); for (int i = 0; i < count; i++) { var key = binaryReader.ReadString(); var value = binaryReader.ReadInt32(); indicatorRecords.Add(key, (QuestIndicatorState)value); } } } }