using System; using System.Collections.Generic; using System.Linq; using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Utility.Raii; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using Microsoft.Extensions.Logging; using Questionable.Controller; using Questionable.Data; using Questionable.Functions; using Questionable.Model; using Questionable.Model.Questing; namespace Questionable.Windows.JournalComponents; internal sealed class QuestJournalUtils { private readonly QuestController _questController; private readonly FateController _fateController; private readonly SeasonalDutyController _seasonalDutyController; private readonly CustomDeliveryController _customDeliveryController; private readonly AttunementController _attunementController; private readonly QuestFunctions _questFunctions; private readonly QuestData _questData; private readonly QuestRegistry _questRegistry; private readonly QuestChainComponent _questChainComponent; private readonly IChatGui _chatGui; private readonly ILogger _logger; private int _availableQuestCount; private long _nextAvailableCountRefreshMs; public QuestJournalUtils(QuestController questController, FateController fateController, SeasonalDutyController seasonalDutyController, CustomDeliveryController customDeliveryController, AttunementController attunementController, QuestFunctions questFunctions, QuestData questData, QuestRegistry questRegistry, QuestChainComponent questChainComponent, IChatGui chatGui, ILogger logger) { _questController = questController; _fateController = fateController; _seasonalDutyController = seasonalDutyController; _customDeliveryController = customDeliveryController; _attunementController = attunementController; _questFunctions = questFunctions; _questData = questData; _questRegistry = questRegistry; _questChainComponent = questChainComponent; _chatGui = chatGui; _logger = logger; } public void ShowContextMenu(IQuestInfo questInfo, Quest? quest, string label) { if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { OpenContextMenu(questInfo); } DrawContextMenu(questInfo, quest, label); } public void OpenContextMenu(IQuestInfo questInfo) { ImU8String strId = new ImU8String(12, 1); strId.AppendLiteral("##QuestPopup"); strId.AppendFormatted(questInfo.QuestId); ImGui.OpenPopup(strId); } public bool DrawContextMenu(IQuestInfo questInfo, Quest? quest, string label) { ImU8String id = new ImU8String(12, 1); id.AppendLiteral("##QuestPopup"); id.AppendFormatted(questInfo.QuestId); using ImRaii.PopupDisposable popupDisposable = ImRaii.Popup(id); if (!popupDisposable) { return false; } using (ImRaii.Disabled(!_questFunctions.IsReadyToAcceptQuest(questInfo.QuestId) || quest == null || quest.Root.Disabled || _fateController.IsRunning || _seasonalDutyController.IsRunning || _customDeliveryController.IsRunning || _attunementController.IsRunning)) { if (ImGui.MenuItem("Start as next quest")) { _fateController.Stop("Quest journal start"); _seasonalDutyController.Stop("Quest journal start"); _customDeliveryController.Stop("Quest journal start"); _attunementController.Stop("Quest journal start"); _questController.SetNextQuest(quest); _questController.Start(label); } } if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) { if (!_questFunctions.IsReadyToAcceptQuest(questInfo.QuestId)) { ImGui.SetTooltip("Quest is not ready to accept"); } else if (quest == null) { ImGui.SetTooltip("Quest has no path available"); } else if (quest.Root.Disabled) { ImGui.SetTooltip("Quest path is disabled"); } } List incompletePrerequisiteQuests = GetIncompletePrerequisiteQuests(questInfo); using (ImRaii.Disabled(incompletePrerequisiteQuests.Count == 0)) { if (ImGui.MenuItem(incompletePrerequisiteQuests.Count switch { 0 => "Add quest and requirements to priority", 1 => "Add 1 quest to priority", _ => $"Add {incompletePrerequisiteQuests.Count} quests to priority", })) { AddRequiredQuestsToPriority(questInfo, incompletePrerequisiteQuests); } } if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) { if (incompletePrerequisiteQuests.Count == 0) { ImGui.SetTooltip("No quests to add (quest may be complete, disabled, locked with no unlock path, or already in priority)"); } else { ImGui.SetTooltip((incompletePrerequisiteQuests.Count == 1) ? "Add this quest to the priority list" : $"Add this quest and {incompletePrerequisiteQuests.Count - 1} required {((incompletePrerequisiteQuests.Count - 1 == 1) ? "quest" : "quests")} to the priority list in completion order"); } } if (label != "QuestChainComponent" && ImGui.MenuItem("View in Quest Chain")) { _questChainComponent.SelectQuest(questInfo.QuestId); _questChainComponent.SelectTabAction?.Invoke("Quest Chain"); } return true; } public unsafe List GetIncompletePrerequisiteQuests(IQuestInfo questInfo) { List list = new List(); HashSet visited = new HashSet(); CollectPrerequisitesRecursive(questInfo, list, visited); list.Reverse(); Quest quest2; List list2 = (from qId in list.Where(delegate(ElementId qId) { if (!_questData.TryGetQuestInfo(qId, out IQuestInfo questInfo2)) { return false; } if (questInfo2.IsRepeatable && qId is QuestId questId2) { if (QuestManager.Instance()->IsDailyQuestCompleted(questId2.Value)) { return false; } } else if (_questFunctions.IsQuestComplete(qId)) { return false; } return true; }) where !_questFunctions.IsQuestUnobtainable(qId) where _questRegistry.IsKnownQuest(qId) where (!_questRegistry.TryGetQuest(qId, out quest2) || !quest2.Root.Disabled) ? true : false where !_questController.ManualPriorityQuests.Any((Quest q) => q.Id.Equals(qId)) select qId).ToList(); list2.Reverse(); bool flag = false; flag = ((!questInfo.IsRepeatable || !(questInfo.QuestId is QuestId questId)) ? (!_questFunctions.IsQuestComplete(questInfo.QuestId)) : (!QuestManager.Instance()->IsDailyQuestCompleted(questId.Value))); Quest quest; bool flag2 = _questRegistry.TryGetQuest(questInfo.QuestId, out quest) && quest.Root.Disabled; bool flag3 = _questFunctions.IsQuestLocked(questInfo.QuestId) && questInfo.PreviousQuests.Count == 0; if (flag && !flag2 && !flag3 && !_questFunctions.IsQuestUnobtainable(questInfo.QuestId) && _questRegistry.IsKnownQuest(questInfo.QuestId) && !_questController.ManualPriorityQuests.Any((Quest q) => q.Id.Equals(questInfo.QuestId))) { list2.Add(questInfo.QuestId); } return list2; } private void CollectPrerequisitesRecursive(IQuestInfo questInfo, List prerequisites, HashSet visited) { if (visited.Contains(questInfo.QuestId)) { return; } visited.Add(questInfo.QuestId); if (questInfo.PreviousQuests.Count <= 0) { return; } if (questInfo.PreviousQuestJoin == EQuestJoin.All) { foreach (PreviousQuestInfo previousQuest in questInfo.PreviousQuests) { if (_questData.TryGetQuestInfo(previousQuest.QuestId, out IQuestInfo questInfo2)) { CollectPrerequisitesRecursive(questInfo2, prerequisites, visited); if (!prerequisites.Contains(previousQuest.QuestId)) { prerequisites.Add(previousQuest.QuestId); } } } return; } if (questInfo.PreviousQuestJoin != EQuestJoin.AtLeastOne) { return; } foreach (PreviousQuestInfo previousQuest2 in questInfo.PreviousQuests) { if (_questFunctions.IsQuestComplete(previousQuest2.QuestId)) { continue; } if (_questData.TryGetQuestInfo(previousQuest2.QuestId, out IQuestInfo questInfo3)) { CollectPrerequisitesRecursive(questInfo3, prerequisites, visited); if (!prerequisites.Contains(previousQuest2.QuestId)) { prerequisites.Add(previousQuest2.QuestId); } } break; } } public void AddRequiredQuestsToPriority(IQuestInfo questInfo, List prerequisiteQuests) { int num = 0; foreach (ElementId prerequisiteQuest in prerequisiteQuests) { if (_questController.AddQuestPriority(prerequisiteQuest)) { num++; } } if (num > 0) { string text = ((num == 1) ? "quest" : "quests"); _logger.LogInformation("Added {Count} {QuestWord} for '{QuestName}' to priority list", num, text, questInfo.Name); _chatGui.Print($"Added {num} {text} to priority list.", "Questionable", 576); } else { _logger.LogWarning("No quests were added to priority list for '{QuestName}'", questInfo.Name); _chatGui.Print("No quests to add (may be complete, disabled, locked with no unlock path, or already in priority).", "Questionable", 576); } } public void AddAllAvailableQuests() { if (Environment.TickCount64 >= _nextAvailableCountRefreshMs) { _nextAvailableCountRefreshMs = Environment.TickCount64 + 1000; try { _availableQuestCount = _questRegistry.AllQuests.Count(delegate(Quest quest) { if (quest?.Info != null) { QuestRoot root = quest.Root; if (root != null && !root.Disabled && !_questFunctions.IsQuestRemoved(quest.Id) && _questFunctions.IsReadyToAcceptQuest(quest.Id) && !_questFunctions.IsQuestComplete(quest.Id) && !_questFunctions.IsQuestAccepted(quest.Id) && !quest.Info.IsRepeatable && !(quest.Info is QuestInfo { MoogleDeliveryLevel: >0 })) { return !_questController.ManualPriorityQuests.Any((Quest q) => q.Id.Equals(quest.Id)); } } return false; }); } catch { _availableQuestCount = 0; } } int availableQuestCount = _availableQuestCount; using (ImRaii.Disabled(availableQuestCount == 0)) { if (UiThemeUtils.IconButton(FontAwesomeIcon.ListOl, ImGui.GetFrameHeight())) { int num = _questController.AddAllAvailableQuests(); string value = ((num == 1) ? "quest" : "quests"); _chatGui.Print($"Added {num} {value} to priority queue.", "Questionable", 576); _nextAvailableCountRefreshMs = 0L; } } if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) { object obj2; switch (availableQuestCount) { case 0: ImGui.SetTooltip("No available quests to add"); return; default: obj2 = "quests"; break; case 1: obj2 = "quest"; break; } string value2 = (string)obj2; ImU8String tooltip = new ImU8String(27, 2); tooltip.AppendLiteral("Add "); tooltip.AppendFormatted(availableQuestCount); tooltip.AppendLiteral(" available "); tooltip.AppendFormatted(value2); tooltip.AppendLiteral(" to priority"); ImGui.SetTooltip(tooltip); } } }