using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using LLib.GameData; using Lumina.Excel; using Lumina.Excel.Sheets; using Questionable.Model.Questing; namespace Questionable.Model; internal sealed class QuestInfo : IQuestInfo { public ElementId QuestId { get; } public string Name { get; } public ushort Level { get; } public uint IssuerDataId { get; } public bool IsRepeatable { get; } public ImmutableList PreviousQuests { get; private set; } public EQuestJoin PreviousQuestJoin { get; } public ImmutableList QuestLocks { get; private set; } public EQuestJoin QuestLockJoin { get; private set; } public List PreviousInstanceContent { get; } public EQuestJoin PreviousInstanceContentJoin { get; } public uint? JournalGenre { get; set; } public ushort SortKey { get; set; } public bool IsMainScenarioQuest { get; } public bool CompletesInstantly { get; } public FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany GrandCompany { get; } public EAlliedSociety AlliedSociety { get; } public byte AlliedSocietyQuestGroup { get; } public int AlliedSocietyRank { get; } public IReadOnlyList ClassJobs { get; } public bool IsSeasonalEvent { get; } public uint NewGamePlusChapter { get; } public byte StartingCity { get; set; } public byte MoogleDeliveryLevel { get; } public bool IsMoogleDeliveryQuest => JournalGenre == 87; public IReadOnlyList ItemRewards { get; } public EExpansionVersion Expansion { get; } public DateTime? SeasonalQuestExpiry { get; internal set; } public bool IsSeasonalQuest { get; internal set; } public QuestInfo(Lumina.Excel.Sheets.Quest quest, uint newGamePlusChapter, byte startingCity, JournalGenreOverrides journalGenreOverrides, bool isSeasonalEventQuest = false, DateTime? seasonalQuestExpiry = null) { QuestId = Questionable.Model.Questing.QuestId.FromRowId(quest.RowId); string value = QuestId.Value switch { 85 => " (Lancer)", 108 => " (Marauder)", 109 => " (Arcanist)", 123 => " (Archer)", 124 => " (Conjurer)", 568 => " (Gladiator)", 569 => " (Pugilist)", 570 => " (Thaumaturge)", 673 => " (Ul'dah)", 674 => " (Limsa/Gridania)", 1432 => " (Gridania)", 1433 => " (Limsa)", 1434 => " (Ul'dah)", _ => "", }; Name = $"{quest.Name}{value}"; Level = quest.ClassJobLevel[0]; IssuerDataId = quest.IssuerStart.RowId; IsRepeatable = quest.IsRepeatable; PreviousQuests = new List { new PreviousQuestInfo(ReplaceOldQuestIds(Questionable.Model.Questing.QuestId.FromRowId(quest.PreviousQuest[0].RowId)), quest.Unknown7), new PreviousQuestInfo(ReplaceOldQuestIds(Questionable.Model.Questing.QuestId.FromRowId(quest.PreviousQuest[1].RowId)), 0), new PreviousQuestInfo(ReplaceOldQuestIds(Questionable.Model.Questing.QuestId.FromRowId(quest.PreviousQuest[2].RowId)), 0) }.Where((PreviousQuestInfo x) => x.QuestId.Value != 0).ToImmutableList(); PreviousQuestJoin = (EQuestJoin)quest.PreviousQuestJoin; QuestLocks = (from x in quest.QuestLock select Questionable.Model.Questing.QuestId.FromRowId(x.RowId) into x where x.Value != 0 select x).ToImmutableList(); QuestLockJoin = (EQuestJoin)quest.QuestLockJoin; (uint?, ushort?) tuple; switch (QuestId.Value) { case 1119: case 1120: case 1121: case 1122: case 1123: case 1124: case 1125: case 1126: case 1127: case 1579: tuple = (journalGenreOverrides.ARelicRebornQuests, (ushort)0); break; case 4196: case 4197: case 4198: case 4199: case 4200: case 4201: case 4202: case 4203: case 4204: case 4205: case 4206: case 4207: case 4208: case 4209: tuple = (journalGenreOverrides.ThavnairSideQuests, null); break; case 4173: tuple = (journalGenreOverrides.RadzAtHanSideQuests, null); break; default: tuple = (quest.JournalGenre.ValueNullable?.RowId, null); break; } (uint?, ushort?) tuple2 = tuple; JournalGenre = tuple2.Item1; SortKey = tuple2.Item2 ?? quest.SortKey; JournalGenre? valueNullable = quest.JournalGenre.ValueNullable; IsMainScenarioQuest = valueNullable.HasValue && valueNullable.GetValueOrDefault().Icon == 61412; CompletesInstantly = quest.TodoParams[0].ToDoCompleteSeq == 0; PreviousInstanceContent = (from x in quest.InstanceContent select (ushort)x.RowId into x where x != 0 select x).ToList(); PreviousInstanceContentJoin = (EQuestJoin)quest.InstanceContentJoin; GrandCompany = (FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany)quest.GrandCompany.RowId; AlliedSociety = (EAlliedSociety)quest.BeastTribe.RowId; AlliedSocietyQuestGroup = quest.DailyQuestPool; AlliedSocietyRank = (int)quest.BeastReputationRank.RowId; ClassJobs = QuestInfoUtils.AsList(quest.ClassJobCategory0.ValueNullable); IsSeasonalEvent = quest.Festival.RowId != 0; IsSeasonalQuest = isSeasonalEventQuest; SeasonalQuestExpiry = (IsSeasonalQuest ? seasonalQuestExpiry : ((DateTime?)null)); SeasonalQuestExpiry = seasonalQuestExpiry; NewGamePlusChapter = newGamePlusChapter; StartingCity = startingCity; MoogleDeliveryLevel = (byte)quest.DeliveryQuest.RowId; ItemRewards = (from Item x in from x in quest.Reward where x.RowId != 0 && x.Is() select x.GetValueOrDefault() into x where x.HasValue select x where x.IsUntradable select ItemReward.CreateFromItem(x, QuestId) into x where x != null select x).Cast().ToList(); Expansion = (EExpansionVersion)quest.Expansion.RowId; } private static QuestId ReplaceOldQuestIds(QuestId questId) { if (questId.Value == 524) { return new QuestId(4522); } return questId; } public void AddPreviousQuest(PreviousQuestInfo questId) { ImmutableList previousQuests = PreviousQuests; int num = 0; PreviousQuestInfo[] array = new PreviousQuestInfo[1 + previousQuests.Count]; foreach (PreviousQuestInfo item in previousQuests) { array[num] = item; num++; } array[num] = questId; PreviousQuests = ImmutableList.Create(new ReadOnlySpan(array)); } public void AddQuestLocks(EQuestJoin questJoin, params QuestId[] questId) { if (QuestLocks.Count > 0 && QuestLockJoin != questJoin) { throw new InvalidOperationException(); } QuestLockJoin = questJoin; ImmutableList questLocks = QuestLocks; int num = 0; QuestId[] array = new QuestId[questLocks.Count + questId.Length]; foreach (QuestId item in questLocks) { array[num] = item; num++; } ReadOnlySpan readOnlySpan = new ReadOnlySpan(questId); readOnlySpan.CopyTo(new Span(array).Slice(num, readOnlySpan.Length)); num += readOnlySpan.Length; QuestLocks = ImmutableList.Create(new ReadOnlySpan(array)); } }