using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Text.RegularExpressions; using System.Text.RegularExpressions.Generated; using Dalamud.Bindings.ImGui; using Dalamud.Game.Text; using Dalamud.Interface; using Dalamud.Interface.Colors; using Dalamud.Interface.Components; using Dalamud.Interface.Utility.Raii; using Dalamud.Plugin.Services; using Microsoft.Extensions.Logging; using Questionable.Controller; using Questionable.Controller.Steps.Shared; using Questionable.Functions; using Questionable.Model; using Questionable.Model.Questing; namespace Questionable.Windows.QuestComponents; internal sealed class ActiveQuestComponent { private readonly QuestController _questController; private readonly MovementController _movementController; private readonly CombatController _combatController; private readonly GatheringController _gatheringController; private readonly QuestFunctions _questFunctions; private readonly ICommandManager _commandManager; private readonly Configuration _configuration; private readonly QuestRegistry _questRegistry; private readonly PriorityWindow _priorityWindow; private readonly UiUtils _uiUtils; private readonly IClientState _clientState; private readonly IChatGui _chatGui; private readonly ILogger _logger; public event EventHandler? Reload; [GeneratedRegex("\\s\\s+", RegexOptions.IgnoreCase, "en-US")] [GeneratedCode("System.Text.RegularExpressions.Generator", "9.0.12.41916")] private static Regex MultipleWhitespaceRegex() { return _003CRegexGenerator_g_003EFBB8301322196CF81C64F1652C2FA6E1D6BF3907141F781E9D97ABED51BF056C4__MultipleWhitespaceRegex_0.Instance; } public ActiveQuestComponent(QuestController questController, MovementController movementController, CombatController combatController, GatheringController gatheringController, QuestFunctions questFunctions, ICommandManager commandManager, Configuration configuration, QuestRegistry questRegistry, PriorityWindow priorityWindow, UiUtils uiUtils, IClientState clientState, IChatGui chatGui, ILogger logger) { _questController = questController; _movementController = movementController; _combatController = combatController; _gatheringController = gatheringController; _questFunctions = questFunctions; _commandManager = commandManager; _configuration = configuration; _questRegistry = questRegistry; _priorityWindow = priorityWindow; _uiUtils = uiUtils; _clientState = clientState; _chatGui = chatGui; _logger = logger; } public void Draw(bool isMinimized) { (QuestController.QuestProgress, QuestController.ECurrentQuestType)? currentQuestDetails = _questController.CurrentQuestDetails; QuestController.QuestProgress questProgress = currentQuestDetails?.Item1; QuestController.ECurrentQuestType? currentQuestType = currentQuestDetails?.Item2; if (questProgress != null) { DrawQuestNames(questProgress, currentQuestType); QuestProgressInfo questProgressInfo = DrawQuestWork(questProgress, isMinimized); if (_combatController.IsRunning) { ImGui.TextColored(ImGuiColors.DalamudOrange, "In Combat"); } else { string currentTaskState = _questController.CurrentTaskState; if (currentTaskState != null) { using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudOrange)) { ImGui.TextUnformatted(currentTaskState); } } else { using (ImRaii.Disabled()) { ImGui.TextUnformatted(_questController.DebugState ?? string.Empty); } } } try { QuestSequence questSequence = questProgress.Quest.FindSequence(questProgress.Sequence); QuestStep questStep = questSequence?.FindStep(questProgress.Step); if (!isMinimized) { using (ImRaii.Color color = new ImRaii.Color()) { bool flag; if (questStep != null) { EInteractionType interactionType = questStep.InteractionType; if (interactionType == EInteractionType.WaitForManualProgress || interactionType == EInteractionType.Snipe || interactionType == EInteractionType.Instruction) { flag = true; goto IL_0154; } } flag = false; goto IL_0154; IL_0154: if (flag) { color.Push(ImGuiCol.Text, ImGuiColors.DalamudOrange); } ImGui.TextUnformatted(questStep?.Comment ?? questSequence?.Comment ?? questProgress.Quest.Root.Comment ?? string.Empty); } ImGui.Text(_questController.ToStatString()); } DrawQuestButtons(questProgress, questStep, questProgressInfo, isMinimized); } catch (Exception ex) { ImGui.TextColored(ImGuiColors.DalamudRed, ex.ToString()); _logger.LogError(ex, "Could not handle active quest buttons"); } DrawSimulationControls(); } else { ImGui.Text("No active quest"); if (!isMinimized) { Vector4 col = ImGuiColors.DalamudGrey; ImU8String text = new ImU8String(14, 1); text.AppendFormatted(_questRegistry.Count); text.AppendLiteral(" quests loaded"); ImGui.TextColored(in col, text); } if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop)) { _movementController.Stop(); _questController.Stop("Manual (no active quest)"); _gatheringController.Stop("Manual (no active quest)"); } ImGui.SameLine(); if (ImGuiComponents.IconButton(FontAwesomeIcon.SortAmountDown)) { _priorityWindow.ToggleOrUncollapse(); } } } private void DrawQuestNames(QuestController.QuestProgress currentQuest, QuestController.ECurrentQuestType? currentQuestType) { if (currentQuestType == QuestController.ECurrentQuestType.Simulated) { using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed)) { ImU8String text = new ImU8String(26, 4); text.AppendLiteral("Simulated Quest: "); text.AppendFormatted(Shorten(currentQuest.Quest.Info.Name)); text.AppendLiteral(" ("); text.AppendFormatted(currentQuest.Quest.Id); text.AppendLiteral(") / "); text.AppendFormatted(currentQuest.Sequence); text.AppendLiteral(" / "); text.AppendFormatted(currentQuest.Step); ImGui.TextUnformatted(text); return; } } if (currentQuestType == QuestController.ECurrentQuestType.Gathering) { using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.ParsedGold)) { ImU8String text = new ImU8String(20, 4); text.AppendLiteral("Gathering: "); text.AppendFormatted(Shorten(currentQuest.Quest.Info.Name)); text.AppendLiteral(" ("); text.AppendFormatted(currentQuest.Quest.Id); text.AppendLiteral(") / "); text.AppendFormatted(currentQuest.Sequence); text.AppendLiteral(" / "); text.AppendFormatted(currentQuest.Step); ImGui.TextUnformatted(text); return; } } QuestController.QuestProgress startedQuest = _questController.StartedQuest; if (startedQuest != null) { if (startedQuest.Quest.Source == Quest.ESource.UserDirectory) { ImGui.PushFont(UiBuilder.IconFont); ImGui.TextColored(ImGuiColors.DalamudOrange, FontAwesomeIcon.FilePen.ToIconString()); ImGui.PopFont(); ImGui.SameLine(0f); if (ImGui.IsItemHovered()) { ImGui.SetTooltip("This quest is loaded from your 'pluginConfigs\\Questionable\\Quests' directory.\nThis gets loaded even if Questionable ships with a newer/different version of the quest."); } } ImU8String text = new ImU8String(16, 4); text.AppendLiteral("Quest: "); text.AppendFormatted(Shorten(startedQuest.Quest.Info.Name)); text.AppendLiteral(" ("); text.AppendFormatted(startedQuest.Quest.Id); text.AppendLiteral(") / "); text.AppendFormatted(startedQuest.Sequence); text.AppendLiteral(" / "); text.AppendFormatted(startedQuest.Step); ImGui.TextUnformatted(text); if (startedQuest.Quest.Root.Disabled) { ImGui.SameLine(); ImGui.TextColored(ImGuiColors.DalamudRed, "Disabled"); } bool flag = _configuration.Stop.Enabled && _configuration.Stop.LevelToStopAfter; bool flag2 = _configuration.Stop.Enabled && _configuration.Stop.QuestsToStopAfter.Any((ElementId x) => !_questFunctions.IsQuestComplete(x) && !_questFunctions.IsQuestUnobtainable(x)); bool flag3 = _configuration.Stop.Enabled && _configuration.Stop.SequenceToStopAfter; if (flag || flag2 || flag3) { ImGui.SameLine(); Vector4 col = ImGuiColors.ParsedPurple; if (flag) { int num = _clientState.LocalPlayer?.Level ?? 0; if (num > 0 && num >= _configuration.Stop.TargetLevel) { col = ImGuiColors.ParsedGreen; } else if (num > 0) { col = ImGuiColors.ParsedBlue; } } if (flag3) { col = ((startedQuest.Sequence < _configuration.Stop.TargetSequence) ? ImGuiColors.ParsedBlue : ImGuiColors.ParsedGreen); } ImGui.TextColored(in col, SeIconChar.Clock.ToIconString()); if (ImGui.IsItemHovered()) { using ImRaii.IEndObject endObject = ImRaii.Tooltip(); if (endObject) { ImGui.Text("Stop Conditions:"); ImGui.Separator(); if (flag) { int num2 = _clientState.LocalPlayer?.Level ?? 0; text = new ImU8String(14, 1); text.AppendLiteral("Stop at level "); text.AppendFormatted(_configuration.Stop.TargetLevel); ImGui.BulletText(text); if (num2 > 0) { ImGui.SameLine(); if (num2 >= _configuration.Stop.TargetLevel) { Vector4 col2 = ImGuiColors.ParsedGreen; text = new ImU8String(22, 1); text.AppendLiteral("(Current: "); text.AppendFormatted(num2); text.AppendLiteral(" - Reached!)"); ImGui.TextColored(in col2, text); } else { Vector4 col2 = ImGuiColors.ParsedBlue; text = new ImU8String(11, 1); text.AppendLiteral("(Current: "); text.AppendFormatted(num2); text.AppendLiteral(")"); ImGui.TextColored(in col2, text); } } } if (flag3) { if (flag) { ImGui.Spacing(); } int sequence = startedQuest.Sequence; text = new ImU8String(23, 1); text.AppendLiteral("Stop at quest sequence "); text.AppendFormatted(_configuration.Stop.TargetSequence); ImGui.BulletText(text); ImGui.SameLine(); if (sequence >= _configuration.Stop.TargetSequence) { Vector4 col2 = ImGuiColors.ParsedGreen; text = new ImU8String(22, 1); text.AppendLiteral("(Current: "); text.AppendFormatted(sequence); text.AppendLiteral(" - Reached!)"); ImGui.TextColored(in col2, text); } else { Vector4 col2 = ImGuiColors.ParsedBlue; text = new ImU8String(11, 1); text.AppendLiteral("(Current: "); text.AppendFormatted(sequence); text.AppendLiteral(")"); ImGui.TextColored(in col2, text); } } if (flag2) { if (flag || flag3) { ImGui.Spacing(); } ImGui.BulletText("Stop after completing any of these quests:"); ImGui.Indent(); foreach (ElementId item in _configuration.Stop.QuestsToStopAfter) { if (_questRegistry.TryGetQuest(item, out Quest quest)) { var (color, icon, _) = _uiUtils.GetQuestStyle(item); _uiUtils.ChecklistItem($"{quest.Info.Name} ({item})", color, icon); } } ImGui.Unindent(); } } } } if (_configuration.Advanced.AdditionalStatusInformation && _questController.IsInterruptible()) { ImGui.SameLine(); ImGui.TextColored(ImGuiColors.DalamudYellow, SeIconChar.Hyadelyn.ToIconString()); if (ImGui.IsItemHovered()) { using ImRaii.IEndObject endObject2 = ImRaii.Tooltip(); if (endObject2) { ImGui.Text("This quest sequence starts with a teleport to an Aetheryte."); ImGui.Text("Certain priority quest (e.g. class quests) may be started/completed by the plugin prior to continuing with this quest."); ImGui.Separator(); ImGui.Text("Available priority quests:"); List nextPriorityQuestsThatCanBeAccepted = _questFunctions.GetNextPriorityQuestsThatCanBeAccepted(); List list = (from x in nextPriorityQuestsThatCanBeAccepted where x.IsAvailable select x.QuestId).ToList(); if (list.Count > 0) { foreach (ElementId item2 in list) { if (_questRegistry.TryGetQuest(item2, out Quest quest2)) { text = new ImU8String(3, 2); text.AppendFormatted(quest2.Info.Name); text.AppendLiteral(" ("); text.AppendFormatted(item2); text.AppendLiteral(")"); ImGui.BulletText(text); } } } else { ImGui.BulletText("(none)"); } if (_configuration.Advanced.AdditionalStatusInformation) { List list2 = nextPriorityQuestsThatCanBeAccepted.Where((PriorityQuestInfo x) => !x.IsAvailable).ToList(); if (list2.Count > 0) { ImGui.Text("Unavailable priority quests:"); foreach (var (elementId2, value) in list2) { if (_questRegistry.TryGetQuest(elementId2, out Quest quest3)) { text = new ImU8String(6, 3); text.AppendFormatted(quest3.Info.Name); text.AppendLiteral(" ("); text.AppendFormatted(elementId2); text.AppendLiteral(") - "); text.AppendFormatted(value); ImGui.BulletText(text); } } } } } } } } QuestController.QuestProgress nextQuest = _questController.NextQuest; if (nextQuest != null) { using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow)) { ImU8String text = new ImU8String(21, 4); text.AppendLiteral("Next Quest: "); text.AppendFormatted(Shorten(nextQuest.Quest.Info.Name)); text.AppendLiteral(" ("); text.AppendFormatted(nextQuest.Quest.Id); text.AppendLiteral(") / "); text.AppendFormatted(nextQuest.Sequence); text.AppendLiteral(" / "); text.AppendFormatted(nextQuest.Step); ImGui.TextUnformatted(text); } } } private unsafe QuestProgressInfo? DrawQuestWork(QuestController.QuestProgress currentQuest, bool isMinimized) { QuestProgressInfo questProgressInfo = _questFunctions.GetQuestProgressInfo(currentQuest.Quest.Id); if (questProgressInfo != null) { if (isMinimized) { return questProgressInfo; } Vector4* styleColorVec = ImGui.GetStyleColorVec4(ImGuiCol.TextDisabled); Vector4 color = ((styleColorVec == null) ? ImGuiColors.ParsedOrange : (*styleColorVec)); using (ImRaii.PushColor(ImGuiCol.Text, color)) { ImU8String text = new ImU8String(0, 1); text.AppendFormatted(questProgressInfo); ImGui.Text(text); if (ImGui.IsItemClicked()) { string text2 = MultipleWhitespaceRegex().Replace(questProgressInfo.ToString(), " "); ImGui.SetClipboardText(text2); _chatGui.Print("Copied '" + text2 + "' to clipboard"); } if (ImGui.IsItemHovered()) { ImGui.SameLine(); ImGui.PushFont(UiBuilder.IconFont); ImGui.Text(FontAwesomeIcon.Copy.ToIconString()); ImGui.PopFont(); } if (currentQuest.Quest.Info.AlliedSociety != EAlliedSociety.None) { ImGui.SameLine(); text = new ImU8String(2, 1); text.AppendLiteral("/ "); text.AppendFormatted(questProgressInfo.ClassJob); ImGui.Text(text); } } } else if (currentQuest.Quest.Id is QuestId) { using (ImRaii.Disabled()) { if (currentQuest.Quest.Id == _questController.NextQuest?.Quest.Id) { ImGui.TextUnformatted("(Next quest in story line not accepted)"); } else { ImGui.TextUnformatted("(Not accepted)"); } } } return questProgressInfo; } private void DrawQuestButtons(QuestController.QuestProgress currentQuest, QuestStep? currentStep, QuestProgressInfo? questProgressInfo, bool isMinimized) { using (ImRaii.Disabled(_questController.IsRunning)) { if (ImGuiComponents.IconButton(FontAwesomeIcon.Play)) { if (questProgressInfo == null) { _questController.SetNextQuest(currentQuest.Quest); } _questController.Start("UI start"); } if (!isMinimized) { ImGui.SameLine(); if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.StepForward, "Step")) { _questController.StartSingleStep("UI step"); } } } ImGui.SameLine(); if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop)) { _movementController.Stop(); _questController.Stop("UI stop"); _gatheringController.Stop("UI stop"); } if (isMinimized) { ImGui.SameLine(); if (ImGuiComponents.IconButton(FontAwesomeIcon.RedoAlt)) { this.Reload?.Invoke(this, EventArgs.Empty); } return; } bool flag = currentStep == currentQuest.Quest.FindSequence(currentQuest.Sequence)?.Steps.LastOrDefault(); WaitAtEnd.WaitNextStepOrSequence task; bool condition = currentStep != null && !flag && currentStep.InteractionType == EInteractionType.Instruction && _questController.HasCurrentTaskMatching(out task); using (ImRaii.Disabled(flag)) { using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.ParsedGreen, condition)) { if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.ArrowCircleRight, "Skip")) { _movementController.Stop(); _questController.Skip(currentQuest.Quest.Id, currentQuest.Sequence); } if (ImGui.IsItemHovered()) { ImGui.SetTooltip("Skip the current step of the quest path."); } } } if (_commandManager.Commands.ContainsKey("/questinfo")) { ImGui.SameLine(); if (ImGuiComponents.IconButton(FontAwesomeIcon.Atlas)) { _commandManager.ProcessCommand($"/questinfo {currentQuest.Quest.Id}"); } if (ImGui.IsItemHovered()) { ImU8String tooltip = new ImU8String(46, 1); tooltip.AppendLiteral("Show information about '"); tooltip.AppendFormatted(currentQuest.Quest.Info.Name); tooltip.AppendLiteral("' in Quest Map plugin."); ImGui.SetTooltip(tooltip); } } } private void DrawSimulationControls() { if (_questController.SimulatedQuest == null) { return; } QuestController.QuestProgress simulatedQuest = _questController.SimulatedQuest; ImGui.Separator(); ImGui.TextColored(ImGuiColors.DalamudRed, "Quest sim active (experimental)"); ImU8String text = new ImU8String(10, 1); text.AppendLiteral("Sequence: "); text.AppendFormatted(simulatedQuest.Sequence); ImGui.Text(text); ImGui.BeginDisabled(simulatedQuest.Sequence == 0); if (ImGuiComponents.IconButton(FontAwesomeIcon.Minus)) { _movementController.Stop(); _questController.Stop("Sim-"); byte oldSequence = simulatedQuest.Sequence; byte sequence = simulatedQuest.Quest.Root.QuestSequence.Select((QuestSequence x) => x.Sequence).LastOrDefault((byte x) => x < oldSequence, 0); _questController.SimulatedQuest.SetSequence(sequence); } ImGui.EndDisabled(); ImGui.SameLine(); ImGui.BeginDisabled(simulatedQuest.Sequence >= byte.MaxValue); if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) { _movementController.Stop(); _questController.Stop("Sim+"); byte oldSequence2 = simulatedQuest.Sequence; byte sequence2 = simulatedQuest.Quest.Root.QuestSequence.Select((QuestSequence x) => x.Sequence).FirstOrDefault((byte x) => x > oldSequence2, byte.MaxValue); simulatedQuest.SetSequence(sequence2); } ImGui.EndDisabled(); QuestSequence questSequence = simulatedQuest.Quest.FindSequence(simulatedQuest.Sequence); if (questSequence == null) { return; } using (ImRaii.PushId("SimulatedStep")) { text = new ImU8String(9, 2); text.AppendLiteral("Step: "); text.AppendFormatted(simulatedQuest.Step); text.AppendLiteral(" / "); text.AppendFormatted(questSequence.Steps.Count - 1); ImGui.Text(text); ImGui.BeginDisabled(simulatedQuest.Step == 0); if (ImGuiComponents.IconButton(FontAwesomeIcon.Minus)) { _movementController.Stop(); _questController.Stop("SimStep-"); simulatedQuest.SetStep(Math.Min(simulatedQuest.Step - 1, questSequence.Steps.Count - 1)); } ImGui.EndDisabled(); ImGui.SameLine(); ImGui.BeginDisabled(simulatedQuest.Step >= questSequence.Steps.Count); if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) { _movementController.Stop(); _questController.Stop("SimStep+"); simulatedQuest.SetStep((simulatedQuest.Step == questSequence.Steps.Count - 1) ? 255 : (simulatedQuest.Step + 1)); } ImGui.EndDisabled(); if (ImGui.Button("Skip current task")) { _questController.SkipSimulatedTask(); } ImGui.SameLine(); if (ImGui.Button("Clear sim")) { _questController.SimulateQuest(null, 0, 0); _movementController.Stop(); _questController.Stop("ClearSim"); } } } private static string Shorten(string text) { if (text.Length > 35) { return string.Concat(text.AsSpan(0, 30).Trim(), ((SeIconChar)57434).ToIconString()); } return text; } }