using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using Dalamud.Game.ClientState.Aetherytes; using Dalamud.Game.Text; using Dalamud.Memory; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.UI; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Component.GUI; using LLib.GameData; using LLib.GameUI; using Lumina.Excel.Sheets; using Microsoft.Extensions.Logging; using Questionable.Controller; using Questionable.Data; using Questionable.Model; using Questionable.Model.Common; using Questionable.Model.Questing; using Questionable.Windows.QuestComponents; namespace Questionable.Functions; internal sealed class QuestFunctions { private readonly QuestRegistry _questRegistry; private readonly QuestData _questData; private readonly AetheryteFunctions _aetheryteFunctions; private readonly AlliedSocietyQuestFunctions _alliedSocietyQuestFunctions; private readonly AlliedSocietyData _alliedSocietyData; private readonly AetheryteData _aetheryteData; private readonly Configuration _configuration; private readonly IDataManager _dataManager; private readonly IClientState _clientState; private readonly IGameGui _gameGui; private readonly IAetheryteList _aetheryteList; private readonly ILogger _logger; private readonly HashSet _alreadyLoggedUnobtainableQuestsDetailed = new HashSet(); private readonly HashSet _alreadyLoggedLevelRequirements = new HashSet(); public QuestFunctions(QuestRegistry questRegistry, QuestData questData, AetheryteFunctions aetheryteFunctions, AlliedSocietyQuestFunctions alliedSocietyQuestFunctions, AlliedSocietyData alliedSocietyData, AetheryteData aetheryteData, Configuration configuration, IDataManager dataManager, IClientState clientState, IGameGui gameGui, IAetheryteList aetheryteList, ILogger logger) { _questRegistry = questRegistry; _questData = questData; _aetheryteFunctions = aetheryteFunctions; _alliedSocietyQuestFunctions = alliedSocietyQuestFunctions; _alliedSocietyData = alliedSocietyData; _aetheryteData = aetheryteData; _configuration = configuration; _dataManager = dataManager; _clientState = clientState; _gameGui = gameGui; _aetheryteList = aetheryteList; _logger = logger; } public unsafe QuestReference GetCurrentQuest(bool allowNewMsq = true) { QuestReference currentQuestInternal = GetCurrentQuestInternal(allowNewMsq); currentQuestInternal.Deconstruct(out ElementId CurrentQuest, out byte Sequence, out MainScenarioQuestState State); ElementId elementId = CurrentQuest; byte sequence = Sequence; MainScenarioQuestState state = State; PlayerState* ptr = PlayerState.Instance(); if (elementId == null || elementId.Value == 0) { if (_clientState.TerritoryType == 181) { return new QuestReference(new QuestId(107), 0, MainScenarioQuestState.Available); } if (_clientState.TerritoryType == 182) { return new QuestReference(new QuestId(594), 0, MainScenarioQuestState.Available); } if (_clientState.TerritoryType == 183) { return new QuestReference(new QuestId(39), 0, MainScenarioQuestState.Available); } return QuestReference.NoQuest(state); } if (elementId.Value == 681) { if (IsQuestAccepted(elementId) || IsQuestComplete(elementId)) { return new QuestReference(elementId, sequence, state); } return _configuration.General.GrandCompany switch { FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.TwinAdder => new QuestReference(new QuestId(680), 0, state), FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.Maelstrom => new QuestReference(new QuestId(681), 0, state), FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.ImmortalFlames => new QuestReference(new QuestId(682), 0, state), _ => QuestReference.NoQuest(MainScenarioQuestState.Unavailable), }; } if (elementId.Value == 3856 && !ptr->IsMountUnlocked(1u)) { ushort num = (FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany)ptr->GrandCompany switch { FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.TwinAdder => 700, FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.Maelstrom => 701, FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.ImmortalFlames => 702, _ => 0, }; if (num != 0 && !QuestManager.IsQuestComplete(num)) { return new QuestReference(new QuestId(num), QuestManager.GetQuestSequence(num), state); } } else if (elementId.Value == 801) { QuestId questId = new QuestId(802); if (IsQuestAccepted(questId)) { return new QuestReference(questId, QuestManager.GetQuestSequence(questId.Value), state); } } return currentQuestInternal; } public unsafe QuestReference GetCurrentQuestInternal(bool allowNewMsq) { QuestManager* ptr = QuestManager.Instance(); if (ptr == null) { return QuestReference.NoQuest(MainScenarioQuestState.Unavailable); } QuestReference questReference = GetMainScenarioQuest().Item1; if (questReference.CurrentQuest != null && !_questRegistry.IsKnownQuest(questReference.CurrentQuest)) { questReference = QuestReference.NoQuest(questReference.State); } if (questReference.CurrentQuest != null && !IsQuestAccepted(questReference.CurrentQuest)) { if (allowNewMsq) { return questReference; } questReference = QuestReference.NoQuest(questReference.State); } List<(ElementId, byte)> list = new List<(ElementId, byte)>(); for (int num = ptr->TrackedQuests.Length - 1; num >= 0; num--) { TrackingWork trackingWork = ptr->TrackedQuests[num]; switch (trackingWork.QuestType) { case 1: { ElementId elementId = new QuestId(ptr->NormalQuests[trackingWork.Index].QuestId); if (_questRegistry.IsKnownQuest(elementId)) { list.Add((elementId, QuestManager.GetQuestSequence(elementId.Value))); } break; } } } if (_configuration.General.SkipLowPriorityDuties && list.Count > 0) { IReadOnlyList<(uint ContentFinderConditionId, ElementId QuestId, int Sequence)> lowPriorityQuests = _questRegistry.LowPriorityContentFinderConditionQuests; list.RemoveAll(((ElementId Quest, byte Sequence) x) => lowPriorityQuests.Any<(uint, ElementId, int)>(((uint ContentFinderConditionId, ElementId QuestId, int Sequence) y) => x.Quest == y.QuestId && x.Sequence == y.Sequence)); } if (list.Count > 0) { (ElementId, byte) tuple = list.First(); ElementId item = tuple.Item1; byte item2 = tuple.Item2; EAlliedSociety firstTrackedAlliedSociety = _alliedSocietyData.GetCommonAlliedSocietyTurnIn(item); if (firstTrackedAlliedSociety != EAlliedSociety.None) { List<(ElementId, byte)> list2 = (from quest in list.Skip(1) where _alliedSocietyData.GetCommonAlliedSocietyTurnIn(quest.Quest) == firstTrackedAlliedSociety select quest).ToList(); if (list2.Count > 0) { if (item2 == byte.MaxValue) { foreach (var (currentQuest, b) in list2) { if (b != byte.MaxValue) { return new QuestReference(currentQuest, b, questReference.State); } } } else if (!IsOnAlliedSocietyMount()) { list2.Insert(0, (item, item2)); _alliedSocietyData.GetCommonAlliedSocietyNpcs(firstTrackedAlliedSociety, out uint[] normalNpcs, out uint[] _); if (normalNpcs.Length != 0) { (ElementId, byte)? tuple3 = (from x in list2 where x.Sequence < byte.MaxValue where IsInteractSequence(x.Quest, x.Sequence, normalNpcs) select x).Cast<(ElementId, byte)?>().FirstOrDefault(); if (tuple3.HasValue) { return new QuestReference(tuple3.Value.Item1, tuple3.Value.Item2, questReference.State); } } } } } return new QuestReference(item, item2, questReference.State); } ElementId elementId2 = (from x in GetNextPriorityQuestsThatCanBeAccepted() where x.IsAvailable select x.QuestId).FirstOrDefault(); if (elementId2 != null) { return new QuestReference(elementId2, QuestManager.GetQuestSequence(elementId2.Value), questReference.State); } if (questReference.CurrentQuest != null) { return questReference; } return QuestReference.NoQuest(questReference.State); } public unsafe (QuestReference, string?) GetMainScenarioQuest() { if (QuestManager.IsQuestComplete(3759)) { AgentInterface* agentByInternalId = AgentModule.Instance()->GetAgentByInternalId(AgentId.QuestRedoHud); if (agentByInternalId != null && agentByInternalId->IsAgentActive()) { AtkUnitBase* addonPtr; bool flag = _gameGui.TryGetAddonByName("QuestRedoHud", out addonPtr) && addonPtr->AtkValuesCount == 4; if (flag) { uint uInt = addonPtr->AtkValues->UInt; bool flag2 = ((uInt == 0 || uInt - 2 <= 2) ? true : false); flag = flag2; } if (flag) { ushort num = MemoryHelper.Read((nint)((byte*)agentByInternalId + 46)); return (new QuestReference(new QuestId(num), QuestManager.GetQuestSequence(num), MainScenarioQuestState.Available), "NG+"); } } } AgentScenarioTree* ptr = AgentScenarioTree.Instance(); if (ptr == null) { return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), "No Scenario Tree"); } if (ptr->Data == null) { return (QuestReference.NoQuest(MainScenarioQuestState.LoadingScreen), "Scenario Tree Data is null"); } QuestId questId = new QuestId(ptr->Data->CurrentScenarioQuest); string item = $"sq: {questId}"; if (questId.Value == 0) { if (IsMainScenarioQuestComplete()) { return (QuestReference.NoQuest(MainScenarioQuestState.Complete), "Main Scenario is complete"); } PlayerState* playerState = PlayerState.Instance(); List list = (from q in _questData.MainScenarioQuests where q.StartingCity == 0 || q.StartingCity == playerState->StartTown where IsReadyToAcceptQuest(q.QuestId, ignoreLevel: true) select q).ToList(); if (list.Count == 0) { return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), "No potential quests found"); } if (list.Count > 1) { if (list.All(delegate(QuestInfo x) { ushort value = x.QuestId.Value; return (uint)(value - 680) <= 2u; })) { questId = new QuestId(681); } else if (list.Any((QuestInfo x) => x.QuestId.Value == 1583)) { questId = new QuestId(1583); } else if (list.Any((QuestInfo x) => x.QuestId.Value == 2451)) { questId = new QuestId(2451); } else if (list.Any((QuestInfo x) => x.QuestId.Value == 3282)) { questId = new QuestId(3282); } else if (list.Any((QuestInfo x) => x.QuestId.Value == 4359)) { questId = new QuestId(4359); } else if (list.Any((QuestInfo x) => x.QuestId.Value == 4865)) { questId = new QuestId(4865); } if (list.Count != 1) { return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), "Multiple potential quests found: " + string.Join(", ", list.Select((QuestInfo x) => x.QuestId.Value))); } } else { questId = (QuestId)list.Single().QuestId; } } QuestManager* ptr2 = QuestManager.Instance(); if (IsQuestAccepted(questId) && ptr2->GetQuestById(questId.Value)->IsHidden) { return (QuestReference.NoQuest(MainScenarioQuestState.Available), "Quest accepted but hidden"); } if (IsQuestComplete(questId)) { return (new QuestReference(questId, byte.MaxValue, MainScenarioQuestState.Available), $"Quest {questId.Value} complete"); } if (!IsReadyToAcceptQuest(questId)) { return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), $"Not readdy to accept quest {questId.Value}"); } byte? b = _clientState.LocalPlayer?.Level; if (!b.HasValue) { return (QuestReference.NoQuest(MainScenarioQuestState.LoadingScreen), "In loading screen"); } if (_questRegistry.TryGetQuest(questId, out Questionable.Model.Quest quest) && quest.Info.Level > b) { return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), "Low level"); } return (new QuestReference(questId, QuestManager.GetQuestSequence(questId.Value), MainScenarioQuestState.Available), item); } private unsafe bool IsOnAlliedSocietyMount() { BattleChara* ptr = (BattleChara*)(_clientState.LocalPlayer?.Address ?? 0); if (ptr != null && ptr->Mount.MountId != 0) { return _alliedSocietyData.Mounts.ContainsKey(ptr->Mount.MountId); } return false; } private bool IsInteractSequence(ElementId questId, byte sequenceNo, uint[] dataIds) { if (_questRegistry.TryGetQuest(questId, out Questionable.Model.Quest quest)) { return quest.FindSequence(sequenceNo)?.Steps.All(delegate(QuestStep x) { if (x == null || x.InteractionType != EInteractionType.WalkTo) { if (x != null && x.InteractionType == EInteractionType.Interact) { uint? dataId = x.DataId; if (dataId.HasValue) { uint valueOrDefault = dataId.GetValueOrDefault(); return dataIds.Contains(valueOrDefault); } } return false; } return true; }) ?? false; } return false; } public unsafe QuestProgressInfo? GetQuestProgressInfo(ElementId elementId) { if (elementId is QuestId questId) { QuestWork* questById = QuestManager.Instance()->GetQuestById(questId.Value); if (questById == null) { return null; } return new QuestProgressInfo(*questById); } return null; } public unsafe List GetNextPriorityQuestsThatCanBeAccepted() { InventoryManager* ptr = InventoryManager.Instance(); int gil = ptr->GetItemCountInContainer(1u, InventoryType.Currency, isHq: false, 0); return (from x in GetPriorityQuests() where IsReadyToAcceptQuest(x) select x).Select(delegate(ElementId x) { if (!_questRegistry.TryGetQuest(x, out Questionable.Model.Quest quest)) { return new PriorityQuestInfo(x, "Unknown quest"); } QuestStep questStep = quest.FindSequence(0)?.FindStep(0); if (questStep == null) { return new PriorityQuestInfo(x, "No sequence 0 with steps"); } if (!_aetheryteFunctions.IsTeleportUnlocked()) { return new PriorityQuestInfo(x, "Teleport not unlocked"); } if (!questStep.IsTeleportableForPriorityQuests()) { return new PriorityQuestInfo(x, "Can't teleport to start"); } DefaultInterpolatedStringHandler handler; if (gil < EstimateTeleportCosts(quest)) { IFormatProvider invariantCulture = CultureInfo.InvariantCulture; handler = new DefaultInterpolatedStringHandler(32, 2, invariantCulture); handler.AppendLiteral("Not enough gil, estimated cost: "); handler.AppendFormatted(EstimateTeleportCosts(quest), "N0"); handler.AppendFormatted(SeIconChar.Gil.ToIconString()); return new PriorityQuestInfo(x, string.Create(invariantCulture, ref handler)); } EAetheryteLocation? value = quest.AllSteps().Select<(QuestSequence, int, QuestStep), EAetheryteLocation?>(delegate((QuestSequence Sequence, int StepId, QuestStep Step) y) { EAetheryteLocation? aetheryteShortcut = y.Step.AetheryteShortcut; if (aetheryteShortcut.HasValue) { EAetheryteLocation valueOrDefault = aetheryteShortcut.GetValueOrDefault(); if (!_aetheryteFunctions.IsAetheryteUnlocked(valueOrDefault)) { SkipConditions? skipConditions = y.Step.SkipConditions; if (skipConditions == null || skipConditions.AetheryteShortcutIf?.AetheryteLocked != valueOrDefault) { return valueOrDefault; } } } AethernetShortcut aethernetShortcut = y.Step.AethernetShortcut; if (aethernetShortcut != null) { if (!_aetheryteFunctions.IsAetheryteUnlocked(aethernetShortcut.From)) { return aethernetShortcut.From; } if (!_aetheryteFunctions.IsAetheryteUnlocked(aethernetShortcut.To)) { return aethernetShortcut.To; } } return (EAetheryteLocation?)null; }).FirstOrDefault((EAetheryteLocation? y) => y.HasValue); if (value.HasValue) { handler = new DefaultInterpolatedStringHandler(18, 1); handler.AppendLiteral("Aetheryte locked: "); handler.AppendFormatted(value); return new PriorityQuestInfo(x, handler.ToStringAndClear()); } return new PriorityQuestInfo(x); }).ToList(); } private int EstimateTeleportCosts(Questionable.Model.Quest quest) { Dictionary cheapTeleports = _aetheryteList.Where((IAetheryteEntry x) => x.IsFavourite || _aetheryteFunctions.IsFreeAetheryte((EAetheryteLocation)x.AetheryteId)).ToDictionary((IAetheryteEntry x) => (EAetheryteLocation)x.AetheryteId, (IAetheryteEntry x) => _aetheryteFunctions.IsFreeAetheryte((EAetheryteLocation)x.AetheryteId) ? 0f : 0.5f); int baseCost = ((quest.Info.Expansion == EExpansionVersion.ARealmReborn) ? 300 : 1000); return (from x in quest.AllSteps() where x.Step.AetheryteShortcut.HasValue select x).Sum<(QuestSequence, int, QuestStep)>(((QuestSequence Sequence, int StepId, QuestStep Step) x) => (int)((float)baseCost * cheapTeleports.GetValueOrDefault(x.Step.AetheryteShortcut.Value, 1f))); } public List GetPriorityQuests(bool onlyClassAndRoleQuests = false) { List list = new List(); if (!onlyClassAndRoleQuests) { if (!_configuration.Advanced.SkipARealmRebornHardModePrimals) { list.AddRange(QuestData.HardModePrimals.Skip(1)); } if (!_configuration.Advanced.SkipCrystalTowerRaids) { list.AddRange(QuestData.CrystalTowerQuests); } } if (!_configuration.Advanced.SkipClassJobQuests) { EClassJob valueOrDefault = ((EClassJob?)_clientState.LocalPlayer?.ClassJob.RowId).GetValueOrDefault(); uint[] shadowbringersRoleQuestChapters = QuestData.AllRoleQuestChapters.Select((IReadOnlyList x) => x[0]).ToArray(); if (valueOrDefault != EClassJob.Adventurer) { list.AddRange(from x in _questRegistry.GetKnownClassJobQuests(valueOrDefault).Where(delegate(QuestInfo x) { if (_questRegistry.TryGetQuest(x.QuestId, out Questionable.Model.Quest quest)) { IQuestInfo info = quest.Info; QuestInfo questInfo = info as QuestInfo; if (questInfo != null) { if (shadowbringersRoleQuestChapters.Contains(questInfo.NewGamePlusChapter)) { return !QuestData.FinalShadowbringersRoleQuests.Any(IsQuestComplete); } if (QuestData.AllRoleQuestChapters.Any((IReadOnlyList y) => y.Contains(questInfo.NewGamePlusChapter))) { return false; } return true; } } return false; }) select x.QuestId); } } return list.Where(_questRegistry.IsKnownQuest).ToList(); } public unsafe bool IsReadyToAcceptQuest(ElementId questId, bool ignoreLevel = false) { _questRegistry.TryGetQuest(questId, out Questionable.Model.Quest quest); if (quest != null) { IQuestInfo info = quest.Info; if (info != null && info.IsRepeatable) { if (IsQuestAccepted(questId)) { return false; } if (questId is QuestId questId2 && IsDailyAlliedSocietyQuest(questId2)) { if (QuestManager.Instance()->IsDailyQuestCompleted(questId.Value)) { return false; } if (!IsDailyAlliedSocietyQuestAndAvailableToday(questId2)) { return false; } } else if (IsQuestComplete(questId)) { return false; } goto IL_0077; } } if (IsQuestAcceptedOrComplete(questId)) { return false; } goto IL_0077; IL_0077: if (IsQuestLocked(questId)) { return false; } if (!ignoreLevel) { byte b = _clientState.LocalPlayer?.Level ?? 0; if (b == 0) { return false; } if (quest != null && quest.Info.Level > b) { if (_alreadyLoggedLevelRequirements.Add(questId.Value)) { _logger.LogDebug("Quest {QuestId} level requirement not met: required {RequiredLevel}, current {CurrentLevel}", questId, quest.Info.Level, b); } return false; } if (quest == null && questId is QuestId questId3 && _questData.TryGetQuestInfo(questId3, out IQuestInfo questInfo) && questInfo is QuestInfo questInfo2 && questInfo2.Level > b) { if (_alreadyLoggedLevelRequirements.Add(questId3.Value)) { _logger.LogDebug("Quest {QuestId} (from data) level requirement not met: required {RequiredLevel}, current {CurrentLevel}", questId3, questInfo2.Level, b); } return false; } } return true; } public bool IsQuestAcceptedOrComplete(ElementId elementId) { if (!IsQuestComplete(elementId)) { return IsQuestAccepted(elementId); } return true; } public bool IsQuestAccepted(ElementId elementId) { if (elementId is QuestId questId) { return IsQuestAccepted(questId); } if (elementId is SatisfactionSupplyNpcId) { return false; } if (elementId is AlliedSocietyDailyId) { return false; } if (elementId is UnlockLinkId) { return false; } if (elementId is AethernetId) { return false; } if (elementId is AetherCurrentId) { return false; } throw new ArgumentOutOfRangeException("elementId"); } public unsafe bool IsQuestAccepted(QuestId questId) { return QuestManager.Instance()->IsQuestAccepted(questId.Value); } public bool IsQuestComplete(ElementId elementId) { if (elementId is QuestId questId) { return IsQuestComplete(questId); } if (elementId is SatisfactionSupplyNpcId) { return false; } if (elementId is AlliedSocietyDailyId) { return false; } if (elementId is UnlockLinkId unlockLinkId) { return IsQuestComplete(unlockLinkId); } if (elementId is AethernetId aethernetId) { return IsQuestComplete(aethernetId); } if (elementId is AetherCurrentId aetherCurrentId) { return IsQuestComplete(aetherCurrentId); } throw new ArgumentOutOfRangeException("elementId"); } public bool IsQuestComplete(QuestId questId) { return QuestManager.IsQuestComplete(questId.Value); } public unsafe bool IsQuestComplete(UnlockLinkId unlockLinkId) { return UIState.Instance()->IsUnlockLinkUnlocked(unlockLinkId.Value); } public bool IsQuestComplete(AethernetId aethernetId) { if (!_questRegistry.TryGetQuest(aethernetId, out Questionable.Model.Quest quest)) { _logger.LogWarning("Aethernet quest {AethernetId} not found in registry", aethernetId); return false; } List<(QuestSequence, int, QuestStep)> list = (from x in quest.AllSteps() where x.Step.InteractionType == EInteractionType.AttuneAethernetShard && x.Step.AethernetShard.HasValue select x).ToList(); if (list.Count == 0) { _logger.LogWarning("Aethernet quest {AethernetId} has no aethernet shard attunement steps", aethernetId); return false; } return list.All<(QuestSequence, int, QuestStep)>(((QuestSequence Sequence, int StepId, QuestStep Step) step) => _aetheryteFunctions.IsAetheryteUnlocked(step.Step.AethernetShard.Value)); } public bool IsQuestComplete(AetherCurrentId aetherCurrentId) { return false; } public bool IsQuestLocked(ElementId elementId, ElementId? extraCompletedQuest = null) { if (elementId is QuestId questId) { return IsQuestLocked(questId, extraCompletedQuest); } if (elementId is SatisfactionSupplyNpcId satisfactionSupplyNpcId) { return IsQuestLocked(satisfactionSupplyNpcId); } if (elementId is AlliedSocietyDailyId alliedSocietyDailyId) { return IsQuestLocked(alliedSocietyDailyId); } if (elementId is UnlockLinkId unlockLinkId) { return IsQuestLocked(unlockLinkId); } if (elementId is AethernetId aethernetId) { return IsQuestLocked(aethernetId); } if (elementId is AetherCurrentId aetherCurrentId) { return IsQuestLocked(aetherCurrentId); } throw new ArgumentOutOfRangeException("elementId"); } private unsafe bool IsQuestLocked(QuestId questId, ElementId? extraCompletedQuest = null) { if (IsQuestUnobtainable(questId, extraCompletedQuest)) { return true; } QuestInfo questInfo = (QuestInfo)_questData.GetQuestInfo(questId); if (questInfo.GrandCompany != FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.None && questInfo.GrandCompany != GetGrandCompany()) { return true; } if (questInfo.AlliedSociety != EAlliedSociety.None && questInfo.IsRepeatable) { return !IsDailyAlliedSocietyQuestAndAvailableToday(questId); } if (questInfo.IsMoogleDeliveryQuest) { byte b = PlayerState.Instance()->DeliveryLevel; if (extraCompletedQuest != null && _questData.TryGetQuestInfo(extraCompletedQuest, out IQuestInfo questInfo2) && questInfo2 is QuestInfo { IsMoogleDeliveryQuest: not false }) { b++; } if (questInfo.MoogleDeliveryLevel > b) { return true; } } if (HasCompletedPreviousQuests(questInfo, extraCompletedQuest)) { return !HasCompletedPreviousInstances(questInfo); } return true; } private bool IsQuestLocked(SatisfactionSupplyNpcId satisfactionSupplyNpcId) { SatisfactionSupplyInfo questInfo = (SatisfactionSupplyInfo)_questData.GetQuestInfo(satisfactionSupplyNpcId); return !HasCompletedPreviousQuests(questInfo, null); } private unsafe bool IsQuestLocked(AlliedSocietyDailyId alliedSocietyDailyId) { byte beastTribeRank = PlayerState.Instance()->GetBeastTribeRank(alliedSocietyDailyId.AlliedSociety); if (beastTribeRank != 0) { return beastTribeRank < alliedSocietyDailyId.Rank; } return true; } private static bool IsQuestLocked(UnlockLinkId unlockLinkId) { return IsQuestUnobtainable(unlockLinkId); } private bool IsQuestLocked(AethernetId aethernetId) { EAetheryteLocation aetheryteLocation = aethernetId.Value switch { 1 => EAetheryteLocation.Limsa, 2 => EAetheryteLocation.Gridania, 3 => EAetheryteLocation.Uldah, 4 => EAetheryteLocation.GoldSaucer, 5 => EAetheryteLocation.Ishgard, 6 => EAetheryteLocation.Idyllshire, 7 => EAetheryteLocation.RhalgrsReach, 8 => EAetheryteLocation.Kugane, 9 => EAetheryteLocation.DomanEnclave, 10 => EAetheryteLocation.Crystarium, 11 => EAetheryteLocation.Eulmore, 12 => EAetheryteLocation.OldSharlayan, 13 => EAetheryteLocation.RadzAtHan, 14 => EAetheryteLocation.Tuliyollal, 15 => EAetheryteLocation.SolutionNine, _ => throw new ArgumentOutOfRangeException("aethernetId", $"Unknown AethernetId: {aethernetId.Value}"), }; if (!_aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation)) { return true; } return false; } private static bool IsQuestLocked(AetherCurrentId aetherCurrentId) { return false; } public bool IsDailyAlliedSocietyQuest(QuestId questId) { QuestInfo questInfo = (QuestInfo)_questData.GetQuestInfo(questId); if (questInfo.AlliedSociety != EAlliedSociety.None) { return questInfo.IsRepeatable; } return false; } public bool IsDailyAlliedSocietyQuestAndAvailableToday(QuestId questId) { if (!IsDailyAlliedSocietyQuest(questId)) { return false; } QuestInfo questInfo = (QuestInfo)_questData.GetQuestInfo(questId); return _alliedSocietyQuestFunctions.GetAvailableAlliedSocietyQuests(questInfo.AlliedSociety).Contains(questId); } public bool IsQuestUnobtainable(ElementId elementId, ElementId? extraCompletedQuest = null) { if (elementId is QuestId questId) { return IsQuestUnobtainable(questId, extraCompletedQuest); } if (elementId is UnlockLinkId unlockLinkId) { return IsQuestUnobtainable(unlockLinkId); } return false; } public unsafe bool IsQuestUnobtainable(QuestId questId, ElementId? extraCompletedQuest = null) { IQuestInfo questInfo = _questData.GetQuestInfo(questId); if (questInfo is UnlockLinkQuestInfo { QuestExpiry: { TimeOfDay: var timeOfDay } questExpiry }) { TimeSpan timeSpan = new TimeSpan(23, 59, 59); bool flag = false; DateTime dateTime; if (timeOfDay == TimeSpan.Zero || timeOfDay == timeSpan) { dateTime = EventInfoComponent.AtDailyReset(DateOnly.FromDateTime(questExpiry)); flag = true; } else { dateTime = ((questExpiry.Kind == DateTimeKind.Utc) ? questExpiry : questExpiry.ToUniversalTime()); } if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value)) { _logger.LogDebug("UnlockLink quest {QuestId} expiry raw={ExpiryRaw} Kind={Kind} TimeOfDay={TimeOfDay}", questId, questExpiry.ToString("o"), questExpiry.Kind, questExpiry.TimeOfDay); _logger.LogDebug("UnlockLink quest {QuestId} normalized expiryUtc={ExpiryUtc:o} treatedAsDailyReset={TreatedAsDailyReset}", questId, dateTime, flag); } if (DateTime.UtcNow > dateTime) { if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value)) { _logger.LogDebug("UnlockLink quest {QuestId} unobtainable: expiry {ExpiryUtc} (UTC) is before now {NowUtc}", questId, dateTime.ToString("o"), DateTime.UtcNow.ToString("o")); } return true; } } QuestInfo questInfo2 = (QuestInfo)questInfo; if ((int)questInfo2.Expansion > (int)PlayerState.Instance()->MaxExpansion) { return true; } if (questInfo2.JournalGenre >= 234 && questInfo2.JournalGenre <= 247) { if (_questRegistry.TryGetQuest(questId, out Questionable.Model.Quest quest)) { List list = quest?.Root?.QuestSequence; if (list != null && list.Count > 0) { goto IL_0288; } } if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value)) { _questData.ApplySeasonalOverride(questId, isSeasonal: true, null); _logger.LogDebug("Quest {QuestId} unobtainable: journal genre is 'event (seasonal)' and no quest path", questId); } return true; } goto IL_0288; IL_0288: if (questInfo2.QuestLocks.Count > 0) { int num = questInfo2.QuestLocks.Count((QuestId x) => IsQuestComplete(x) || x.Equals(extraCompletedQuest)); if (questInfo2.QuestLockJoin == EQuestJoin.All && questInfo2.QuestLocks.Count == num) { return true; } if (questInfo2.QuestLockJoin == EQuestJoin.AtLeastOne && num > 0) { return true; } } DateTime? seasonalQuestExpiry = questInfo2.SeasonalQuestExpiry; if (seasonalQuestExpiry.HasValue) { DateTime valueOrDefault = seasonalQuestExpiry.GetValueOrDefault(); TimeSpan timeOfDay2 = valueOrDefault.TimeOfDay; TimeSpan timeSpan2 = new TimeSpan(23, 59, 59); bool flag2 = false; DateTime dateTime2; if (timeOfDay2 == TimeSpan.Zero || timeOfDay2 == timeSpan2) { dateTime2 = EventInfoComponent.AtDailyReset(DateOnly.FromDateTime(valueOrDefault)); flag2 = true; } else { dateTime2 = ((valueOrDefault.Kind == DateTimeKind.Utc) ? valueOrDefault : valueOrDefault.ToUniversalTime()); } if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value)) { _logger.LogDebug("Quest {QuestId} seasonal expiry raw={ExpiryRaw} Kind={Kind} TimeOfDay={TimeOfDay}", questId, valueOrDefault.ToString("o"), valueOrDefault.Kind, valueOrDefault.TimeOfDay); _logger.LogDebug("Quest {QuestId} normalized expiryUtc={ExpiryUtc:o} treatedAsDailyReset={TreatedAsDailyReset}", questId, dateTime2, flag2); _logger.LogTrace("Quest {QuestId} expiry check: nowUtc={Now:o}, expiryUtc={Expiry:o}, expired={Expired}", questId, DateTime.UtcNow, dateTime2, DateTime.UtcNow > dateTime2); } if (DateTime.UtcNow > dateTime2) { if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value)) { _logger.LogDebug("Quest {QuestId} unobtainable: seasonal expiry {ExpiryUtc} (UTC) is before now {NowUtc}", questId, dateTime2.ToString("o"), DateTime.UtcNow.ToString("o")); } return true; } } if ((questInfo2.IsSeasonalEvent || questInfo2.IsSeasonalQuest) && !(questInfo2.SeasonalQuestExpiry is DateTime)) { if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value)) { _logger.LogDebug("Quest {QuestId} is seasonal/event with no expiry; ShowIncompleteSeasonalEvents={ShowIncomplete}", questId, _configuration.General.ShowIncompleteSeasonalEvents); } if (!_configuration.General.ShowIncompleteSeasonalEvents) { return true; } } if (_questData.GetLockedClassQuests().Contains(questId)) { return true; } byte startTown = PlayerState.Instance()->StartTown; if (questInfo2.StartingCity > 0 && questInfo2.StartingCity != startTown) { return true; } if (questId.Value == 674 && startTown == 3) { return true; } if (questId.Value == 673 && startTown != 3) { return true; } Dictionary dictionary = new Dictionary { { 108, EClassJob.Marauder }, { 109, EClassJob.Arcanist }, { 85, EClassJob.Lancer }, { 123, EClassJob.Archer }, { 124, EClassJob.Conjurer }, { 568, EClassJob.Gladiator }, { 569, EClassJob.Pugilist }, { 570, EClassJob.Thaumaturge } }; if (dictionary.TryGetValue(questId.Value, out var value) && dictionary.Any((KeyValuePair x) => IsQuestAcceptedOrComplete(new QuestId(x.Key))) && (EClassJob)PlayerState.Instance()->FirstClass != value) { return true; } if (IsQuestRemoved(questId)) { return true; } return false; } private static bool IsQuestUnobtainable(UnlockLinkId unlockLinkId) { if (unlockLinkId.Value == 506) { return !IsFestivalActive(160, (ushort)2); } if (unlockLinkId.Value == 568) { return !IsFestivalActive(160, (ushort)3); } return true; } private unsafe static bool IsFestivalActive(ushort id, ushort? phase = null) { for (int i = 0; i < GameMain.Instance()->ActiveFestivals.Length; i++) { GameMain.Festival festival = GameMain.Instance()->ActiveFestivals[i]; if (festival.Id == id) { if (phase.HasValue) { return festival.Phase == phase; } return true; } } return false; } public bool IsQuestRemoved(ElementId elementId) { if (elementId is QuestId questId) { return IsQuestRemoved(questId); } return false; } private bool IsQuestRemoved(QuestId questId) { ushort value = questId.Value; if (value == 487 || (uint)(value - 1428) <= 1u) { return true; } return false; } private bool HasCompletedPreviousQuests(IQuestInfo questInfo, ElementId? extraCompletedQuest) { if (questInfo.PreviousQuests.Count == 0) { return true; } int num = questInfo.PreviousQuests.Count((PreviousQuestInfo x) => HasEnoughProgressOnPreviousQuest(x) || x.QuestId.Equals(extraCompletedQuest)); if (questInfo.PreviousQuestJoin == EQuestJoin.All && questInfo.PreviousQuests.Count == num) { return true; } if (questInfo.PreviousQuestJoin == EQuestJoin.AtLeastOne && num > 0) { return true; } return false; } private bool HasEnoughProgressOnPreviousQuest(PreviousQuestInfo previousQuestInfo) { if (IsQuestComplete(previousQuestInfo.QuestId)) { return true; } if (previousQuestInfo.Sequence != 0 && IsQuestAccepted(previousQuestInfo.QuestId)) { QuestProgressInfo questProgressInfo = GetQuestProgressInfo(previousQuestInfo.QuestId); if (questProgressInfo != null) { return questProgressInfo.Sequence >= previousQuestInfo.Sequence; } return false; } return false; } private static bool HasCompletedPreviousInstances(QuestInfo questInfo) { if (questInfo.PreviousInstanceContent.Count == 0) { return true; } int num = questInfo.PreviousInstanceContent.Count((ushort x) => UIState.IsInstanceContentCompleted(x)); if (questInfo.PreviousInstanceContentJoin == EQuestJoin.All && questInfo.PreviousInstanceContent.Count == num) { return true; } if (questInfo.PreviousInstanceContentJoin == EQuestJoin.AtLeastOne && num > 0) { return true; } return false; } public unsafe bool IsClassJobUnlocked(EClassJob classJob) { ClassJob row = _dataManager.GetExcelSheet().GetRow((uint)classJob); ushort num = (ushort)row.UnlockQuest.RowId; if (num != 0) { return IsQuestComplete(new QuestId(num)); } PlayerState* ptr = PlayerState.Instance(); if (ptr != null) { return ptr->ClassJobLevels[row.ExpArrayIndex] > 0; } return false; } public bool IsJobUnlocked(EClassJob classJob) { return IsClassJobUnlocked((EClassJob)_dataManager.GetExcelSheet().GetRow((uint)classJob).ClassJobParent.RowId); } public unsafe FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany GetGrandCompany() { return (FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany)PlayerState.Instance()->GrandCompany; } public bool IsMainScenarioQuestComplete() { return IsQuestComplete(_questData.LastMainScenarioQuestId); } }