using System; using System.Collections.Generic; using System.Linq; namespace Server.Engines.Quests { public class QuestSerializer { public static object Construct(Type type) { try { return Activator.CreateInstance(type); } catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); return null; } } public static QuestSystem DeserializeQuest(GenericReader reader) { int encoding = reader.ReadEncodedInt(); switch (encoding) { default: case 0x00: // null { return null; } case 0x01: // old format { Type type = ReadQuestType(reader); QuestSystem qs = Construct(type) as QuestSystem; if (qs == null) QuestSystemStub.Instance.BaseDeserialize(reader); else qs.BaseDeserialize(reader); return qs; } case 0x02: { Type type = reader.ReadObjectType(); QuestSystem qs = Construct(type) as QuestSystem; Persistence.DeserializeBlock(reader, r => qs?.BaseDeserialize(r)); return qs; } } } public static void Serialize(QuestSystem qs, GenericWriter writer) { if (qs == null) { writer.WriteEncodedInt(0x00); } else { writer.WriteEncodedInt(0x02); writer.WriteObjectType(qs); Persistence.SerializeBlock(writer, qs.BaseSerialize); } } public static QuestObjective DeserializeObjective(QuestSystem qs, GenericReader reader) { int encoding = reader.ReadEncodedInt(); switch (encoding) { default: case 0x00: // null { return null; } case 0x01: // old format { Type type = ReadType(qs, reader); QuestObjective obj = Construct(type) as QuestObjective; if (obj == null) QuestObjectiveStub.Instance.BaseDeserialize(reader); else obj.BaseDeserialize(reader); return obj; } case 0x02: { Type type = reader.ReadObjectType(); QuestObjective obj = Construct(type) as QuestObjective; Persistence.DeserializeBlock(reader, r => obj?.BaseDeserialize(r)); return obj; } } } public static void Serialize(QuestSystem qs, QuestObjective obj, GenericWriter writer) { if (obj == null) { writer.WriteEncodedInt(0x00); } else { writer.WriteEncodedInt(0x02); writer.WriteObjectType(obj); Persistence.SerializeBlock(writer, obj.BaseSerialize); } } public static QuestConversation DeserializeConversation(QuestSystem qs, GenericReader reader) { int encoding = reader.ReadEncodedInt(); switch (encoding) { default: case 0x00: // null { return null; } case 0x01: // old format { Type type = ReadType(qs, reader); QuestConversation conv = Construct(type) as QuestConversation; if (conv == null) QuestConversationStub.Instance.BaseDeserialize(reader); else conv.BaseDeserialize(reader); return conv; } case 0x02: { Type type = reader.ReadObjectType(); QuestConversation conv = Construct(type) as QuestConversation; Persistence.DeserializeBlock(reader, r => conv?.BaseDeserialize(r)); return conv; } } } public static void Serialize(QuestSystem qs, QuestConversation conv, GenericWriter writer) { if (conv == null) { writer.WriteEncodedInt(0x00); } else { writer.WriteEncodedInt(0x02); writer.WriteObjectType(conv); Persistence.SerializeBlock(writer, conv.BaseSerialize); } } #region Old Serialization /// /// This dictionary does not need to be updated - not even if any quests change. /// It is used strictly for loading the old serialization format. /// private static readonly Dictionary _QuestTable = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["Server.Engines.Quests.Doom.TheSummoningQuest"] = new[] { "AcceptConversation", "CollectBonesObjective", "VanquishDaemonConversation", "VanquishDaemonObjective" }, // Deprecated ["Server.Engines.Quests.Necro.DarkTidesQuest"] = new[] { "AcceptConversation", "AnimateMaabusCorpseObjective", "BankerConversation", "CashBankCheckObjective", "FetchAbraxusScrollObjective", "FindBankObjective", "FindCallingScrollObjective", "FindCityOfLightObjective", "FindCrystalCaveObjective", "FindMaabusCorpseObjective", "FindMaabusTombObjective", "FindMardothAboutKronusObjective", "FindMardothAboutVaultObjective", "FindMardothEndObjective", "FindVaultOfSecretsObjective", "FindWellOfTearsObjective", "HorusConversation", "LostCallingScrollConversation", "MaabasConversation", "MardothEndConversation", "MardothKronusConversation", "MardothVaultConversation", "RadarConversation", "ReadAbraxusScrollConversation", "ReadAbraxusScrollObjective", "ReanimateMaabusConversation", "RetrieveAbraxusScrollObjective", "ReturnToCrystalCaveObjective", "SecondHorusConversation", "SpeakCavePasswordObjective", "UseCallingScrollObjective", "VaultOfSecretsConversation", "FindHorusAboutRewardObjective", "HealConversation", "HorusRewardConversation" }, // Deprecated ["Server.Engines.Quests.Haven.UzeraanTurmoilQuest"] = new[] { "AcceptConversation", "UzeraanTitheConversation", "UzeraanFirstTaskConversation", "UzeraanReportConversation", "SchmendrickConversation", "UzeraanScrollOfPowerConversation", "DryadConversation", "UzeraanFertileDirtConversation", "UzeraanDaemonBloodConversation", "UzeraanDaemonBoneConversation", "BankerConversation", "RadarConversation", "LostScrollOfPowerConversation", "LostFertileDirtConversation", "DryadAppleConversation", "LostDaemonBloodConversation", "LostDaemonBoneConversation", "FindUzeraanBeginObjective", "TitheGoldObjective", "FindUzeraanFirstTaskObjective", "KillHordeMinionsObjective", "FindUzeraanAboutReportObjective", "FindSchmendrickObjective", "FindApprenticeObjective", "ReturnScrollOfPowerObjective", "FindDryadObjective", "ReturnFertileDirtObjective", "GetDaemonBloodObjective", "ReturnDaemonBloodObjective", "GetDaemonBoneObjective", "ReturnDaemonBoneObjective", "CashBankCheckObjective", "FewReagentsConversation" }, ["Server.Engines.Quests.Collector.CollectorQuest"] = new[] { "DontOfferConversation", "DeclineConversation", "AcceptConversation", "ElwoodDuringFishConversation", "ReturnPearlsConversation", "AlbertaPaintingConversation", "AlbertaStoolConversation", "AlbertaEndPaintingConversation", "AlbertaAfterPaintingConversation", "ElwoodDuringPainting1Conversation", "ElwoodDuringPainting2Conversation", "ReturnPaintingConversation", "GabrielAutographConversation", "GabrielNoSheetMusicConversation", "NoSheetMusicConversation", "GetSheetMusicConversation", "GabrielSheetMusicConversation", "GabrielIgnoreConversation", "ElwoodDuringAutograph1Conversation", "ElwoodDuringAutograph2Conversation", "ElwoodDuringAutograph3Conversation", "ReturnAutographConversation", "TomasToysConversation", "TomasDuringCollectingConversation", "ReturnImagesConversation", "ElwoodDuringToys1Conversation", "ElwoodDuringToys2Conversation", "ElwoodDuringToys3Conversation", "FullEndConversation", "FishPearlsObjective", "ReturnPearlsObjective", "FindAlbertaObjective", "SitOnTheStoolObjective", "ReturnPaintingObjective", "FindGabrielObjective", "FindSheetMusicObjective", "ReturnSheetMusicObjective", "ReturnAutographObjective", "FindTomasObjective", "CaptureImagesObjective", "ReturnImagesObjective", "ReturnToysObjective", "MakeRoomObjective" }, ["Server.Engines.Quests.Hag.WitchApprenticeQuest"] = new[] { "FindApprenticeObjective", "FindGrizeldaAboutMurderObjective", "KillImpsObjective", "FindZeefzorpulObjective", "ReturnRecipeObjective", "FindIngredientObjective", "ReturnIngredientsObjective", "DontOfferConversation", "AcceptConversation", "HagDuringCorpseSearchConversation", "ApprenticeCorpseConversation", "MurderConversation", "HagDuringImpSearchConversation", "ImpDeathConversation", "ZeefzorpulConversation", "RecipeConversation", "HagDuringIngredientsConversation", "BlackheartFirstConversation", "BlackheartNoPirateConversation", "BlackheartPirateConversation", "EndConversation", "RecentlyFinishedConversation" }, ["Server.Engines.Quests.Naturalist.StudyOfSolenQuest"] = new[] { "StudyNestsObjective", "ReturnToNaturalistObjective", "DontOfferConversation", "AcceptConversation", "NaturalistDuringStudyConversation", "EndConversation", "SpecialEndConversation", "FullBackpackConversation" }, ["Server.Engines.Quests.Matriarch.SolenMatriarchQuest"] = new[] { "DontOfferConversation", "AcceptConversation", "DuringKillInfiltratorsConversation", "GatherWaterConversation", "DuringWaterGatheringConversation", "ProcessFungiConversation", "DuringFungiProcessConversation", "FullBackpackConversation", "EndConversation", "KillInfiltratorsObjective", "ReturnAfterKillsObjective", "GatherWaterObjective", "ReturnAfterWaterObjective", "ProcessFungiObjective", "GetRewardObjective" }, ["Server.Engines.Quests.Ambitious.AmbitiousQueenQuest"] = new[] { "DontOfferConversation", "AcceptConversation", "DuringKillQueensConversation", "GatherFungiConversation", "DuringFungiGatheringConversation", "EndConversation", "FullBackpackConversation", "End2Conversation", "KillQueensObjective", "ReturnAfterKillsObjective", "GatherFungiObjective", "GetRewardObjective" }, // Deprecated ["Server.Engines.Quests.Ninja.EminosUndertakingQuest"] = new[] { "AcceptConversation", "FindZoelConversation", "RadarConversation", "EnterCaveConversation", "SneakPastGuardiansConversation", "NeedToHideConversation", "UseTeleporterConversation", "GiveZoelNoteConversation", "LostNoteConversation", "GainInnInformationConversation", "ReturnFromInnConversation", "SearchForSwordConversation", "HallwayWalkConversation", "ReturnSwordConversation", "SlayHenchmenConversation", "ContinueSlayHenchmenConversation", "GiveEminoSwordConversation", "LostSwordConversation", "EarnGiftsConversation", "EarnLessGiftsConversation", "FindEminoBeginObjective", "FindZoelObjective", "EnterCaveObjective", "SneakPastGuardiansObjective", "UseTeleporterObjective", "GiveZoelNoteObjective", "GainInnInformationObjective", "ReturnFromInnObjective", "SearchForSwordObjective", "HallwayWalkObjective", "ReturnSwordObjective", "SlayHenchmenObjective", "GiveEminoSwordObjective" }, // Deprecated ["Server.Engines.Quests.Samurai.HaochisTrialsQuest"] = new[] { "AcceptConversation", "RadarConversation", "FirstTrialIntroConversation", "FirstTrialKillConversation", "GainKarmaConversation", "SecondTrialIntroConversation", "SecondTrialAttackConversation", "ThirdTrialIntroConversation", "ThirdTrialKillConversation", "FourthTrialIntroConversation", "FourthTrialCatsConversation", "FifthTrialIntroConversation", "FifthTrialReturnConversation", "LostSwordConversation", "SixthTrialIntroConversation", "SeventhTrialIntroConversation", "EndConversation", "FindHaochiObjective", "FirstTrialIntroObjective", "FirstTrialKillObjective", "FirstTrialReturnObjective", "SecondTrialIntroObjective", "SecondTrialAttackObjective", "SecondTrialReturnObjective", "ThirdTrialIntroObjective", "ThirdTrialKillObjective", "ThirdTrialReturnObjective", "FourthTrialIntroObjective", "FourthTrialCatsObjective", "FourthTrialReturnObjective", "FifthTrialIntroObjective", "FifthTrialReturnObjective", "SixthTrialIntroObjective", "SixthTrialReturnObjective", "SeventhTrialIntroObjective", "SeventhTrialReturnObjective" }, ["Server.Engines.Quests.Zento.TerribleHatchlingsQuest"] = new[] { "AcceptConversation", "DirectionConversation", "TakeCareConversation", "EndConversation", "FirstKillObjective", "SecondKillObjective", "ThirdKillObjective", "ReturnObjective" }, ["Server.Engines.Quests.TimeLord.TimeForLegendsQuest"] = new[] { "TimeForLegendsObjective" }, }; private static readonly string[] _QuestTypes = _QuestTable.Keys.ToArray(); /// /// This method is strictly used for loading the old quest serialization format. /// public static Type ReadQuestType(GenericReader reader) { return ReadType((QuestSystem)null, reader); } private static Type ReadType(QuestSystem qs, GenericReader reader) { string[] referenceTable; if (qs != null) { Type type = qs.GetType(); if (_QuestTable.TryGetValue(type.FullName, out referenceTable) && referenceTable.Length > 0 && !referenceTable[0].Contains('.')) { for (int i = 0; i < referenceTable.Length; i++) referenceTable[i] = $"{type.Namespace}.{referenceTable[i]}"; } } else referenceTable = _QuestTypes; return ReadType(referenceTable, reader); } private static Type ReadType(string[] referenceTable, GenericReader reader) { int encoding = reader.ReadEncodedInt(); switch (encoding) { default: case 0x00: // null { return null; } case 0x01: // indexed { int index = reader.ReadEncodedInt(); if (index >= 0 && index < referenceTable.Length) return ScriptCompiler.FindTypeByFullName(referenceTable[index]); return null; } case 0x02: // by name { string fullName = reader.ReadString(); if (fullName == null) return null; return ScriptCompiler.FindTypeByFullName(fullName, false); } } } private sealed class QuestSystemStub : QuestSystem { public static QuestSystemStub Instance => new QuestSystemStub(); public override object Name { get; } = string.Empty; public override object OfferMessage { get; } = 0; public override int Picture { get; } = 0; public override bool IsTutorial { get; } = false; public override TimeSpan RestartDelay { get; } = TimeSpan.Zero; private QuestSystemStub() { } } private sealed class QuestObjectiveStub : QuestObjective { public static QuestObjectiveStub Instance => new QuestObjectiveStub(); public override object Message { get; } = 0; private QuestObjectiveStub() { } } private sealed class QuestConversationStub : QuestConversation { public static QuestConversationStub Instance => new QuestConversationStub(); public override object Message { get; } = 0; private QuestConversationStub() { } } #endregion } }