-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Dalamud.Configuration;
using Dalamud.Game.Text;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using LLib.GameData;
using LLib.ImGui;
+using Newtonsoft.Json;
+using Questionable.Model.Questing;
namespace Questionable;
public int Version { get; set; } = 1;
public int PluginSetupCompleteVersion { get; set; }
public GeneralConfiguration General { get; } = new();
+ public StopConfiguration Stop { get; } = new();
public DutyConfiguration Duties { get; } = new();
public SinglePlayerDutyConfiguration SinglePlayerDuties { get; } = new();
public NotificationConfiguration Notifications { get; } = new();
public bool ConfigureTextAdvance { get; set; } = true;
}
+ internal sealed class StopConfiguration
+ {
+ public bool Enabled { get; set; }
+
+ [JsonProperty(ItemConverterType = typeof(ElementIdNConverter))]
+ public List<ElementId> QuestsToStopAfter { get; set; } = [];
+ }
+
internal sealed class DutyConfiguration
{
public bool RunInstancedContentWithAutoDuty { get; set; }
WrathCombo,
RotationSolverReborn,
}
+
+ public sealed class ElementIdNConverter : JsonConverter<ElementId>
+ {
+ public override void WriteJson(JsonWriter writer, ElementId? value, JsonSerializer serializer)
+ {
+ writer.WriteValue(value?.ToString());
+ }
+
+ public override ElementId? ReadJson(JsonReader reader, Type objectType, ElementId? existingValue,
+ bool hasExistingValue, JsonSerializer serializer)
+ {
+ string? value = reader.Value?.ToString();
+ return value != null ? ElementId.FromString(value) : null;
+ }
+ }
}
}
else if (_startedQuest == null || _startedQuest.Quest.Id != currentQuestId)
{
- if (_questRegistry.TryGetQuest(currentQuestId, out var quest))
+ if (_configuration.Stop.Enabled &&
+ _startedQuest != null &&
+ _configuration.Stop.QuestsToStopAfter.Contains(_startedQuest.Quest.Id) &&
+ _questFunctions.IsQuestComplete(_startedQuest.Quest.Id))
+ {
+ var questId = _startedQuest.Quest.Id;
+ _logger.LogInformation("Reached stopping point (quest: {QuestId})", questId);
+ _chatGui.Print($"Completed quest '{_startedQuest.Quest.Info.Name}', which is configured as a stopping point.", CommandHandler.MessageTag, CommandHandler.TagColor);
+ _startedQuest = null;
+ Stop($"Stopping point [{questId}] reached");
+ }
+ else if (_questRegistry.TryGetQuest(currentQuestId, out var quest))
{
_logger.LogInformation("New quest: {QuestName}", quest.Info.Name);
_startedQuest = new QuestProgress(quest, currentSequence);
using Questionable.Windows.ConfigComponents;
using Questionable.Windows.JournalComponents;
using Questionable.Windows.QuestComponents;
+using Questionable.Windows.Utils;
using Action = Questionable.Controller.Steps.Interactions.Action;
namespace Questionable;
private static void AddWindows(ServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<UiUtils>();
+ serviceCollection.AddTransient<QuestSelector>();
serviceCollection.AddSingleton<ActiveQuestComponent>();
serviceCollection.AddSingleton<ARealmRebornComponent>();
serviceCollection.AddSingleton<PluginConfigComponent>();
serviceCollection.AddSingleton<DutyConfigComponent>();
serviceCollection.AddSingleton<SinglePlayerDutyConfigComponent>();
+ serviceCollection.AddSingleton<StopConditionComponent>();
serviceCollection.AddSingleton<NotificationConfigComponent>();
serviceCollection.AddSingleton<DebugConfigComponent>();
}
--- /dev/null
+using System.Collections.Generic;
+using System.Linq;
+using Dalamud.Bindings.ImGui;
+using Dalamud.Interface;
+using Dalamud.Interface.Components;
+using Dalamud.Interface.Utility.Raii;
+using Dalamud.Plugin;
+using Questionable.Controller;
+using Questionable.Functions;
+using Questionable.Model;
+using Questionable.Model.Questing;
+using Questionable.Windows.QuestComponents;
+using Questionable.Windows.Utils;
+
+namespace Questionable.Windows.ConfigComponents;
+
+internal sealed class StopConditionComponent : ConfigComponent
+{
+ private readonly IDalamudPluginInterface _pluginInterface;
+ private readonly QuestSelector _questSelector;
+ private readonly QuestRegistry _questRegistry;
+ private readonly QuestTooltipComponent _questTooltipComponent;
+ private readonly UiUtils _uiUtils;
+
+ public StopConditionComponent(
+ IDalamudPluginInterface pluginInterface,
+ QuestSelector questSelector,
+ QuestFunctions questFunctions,
+ QuestRegistry questRegistry,
+ QuestTooltipComponent questTooltipComponent,
+ UiUtils uiUtils,
+ Configuration configuration)
+ : base(pluginInterface, configuration)
+ {
+ _pluginInterface = pluginInterface;
+ _questSelector = questSelector;
+ _questRegistry = questRegistry;
+ _questTooltipComponent = questTooltipComponent;
+ _uiUtils = uiUtils;
+
+ _questSelector.SuggestionPredicate = quest => configuration.Stop.QuestsToStopAfter.All(x => x != quest.Id);
+ _questSelector.DefaultPredicate = quest => quest.Info.IsMainScenarioQuest && questFunctions.IsQuestAccepted(quest.Id);
+ _questSelector.QuestSelected = quest =>
+ {
+ configuration.Stop.QuestsToStopAfter.Add(quest.Id);
+ Save();
+ };
+ }
+
+ public override void DrawTab()
+ {
+ using var tab = ImRaii.TabItem("Stop###StopConditionns");
+ if (!tab)
+ return;
+
+ bool enabled = Configuration.Stop.Enabled;
+ if (ImGui.Checkbox("Stop Questionable when completing any of the quests selected below", ref enabled))
+ {
+ Configuration.Stop.Enabled = enabled;
+ Save();
+ }
+
+ ImGui.Separator();
+
+ using (ImRaii.Disabled(!enabled))
+ {
+ ImGui.Text("Quests to stop after:");
+
+ _questSelector.DrawSelection();
+
+ List<ElementId> questsToStopAfter = Configuration.Stop.QuestsToStopAfter;
+ Quest? itemToRemove = null;
+ for (int i = 0; i < questsToStopAfter.Count; i++)
+ {
+ ElementId questId = questsToStopAfter[i];
+
+ if (!_questRegistry.TryGetQuest(questId, out Quest? quest))
+ continue;
+
+ using (ImRaii.PushId($"Quest{questId}"))
+ {
+ var style = _uiUtils.GetQuestStyle(questId);
+ bool hovered;
+ using (var _ = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
+ {
+ ImGui.AlignTextToFramePadding();
+ ImGui.TextColored(style.Color, style.Icon.ToIconString());
+ hovered = ImGui.IsItemHovered();
+ }
+
+ ImGui.SameLine();
+ ImGui.AlignTextToFramePadding();
+ ImGui.Text(quest.Info.Name);
+ hovered |= ImGui.IsItemHovered();
+
+ if (hovered)
+ _questTooltipComponent.Draw(quest.Info);
+
+ using (ImRaii.PushFont(UiBuilder.IconFont))
+ {
+ ImGui.SameLine(ImGui.GetContentRegionAvail().X +
+ ImGui.GetStyle().WindowPadding.X -
+ ImGui.CalcTextSize(FontAwesomeIcon.Times.ToIconString()).X -
+ ImGui.GetStyle().FramePadding.X * 2);
+ }
+
+ if (ImGuiComponents.IconButton($"##Remove{i}", FontAwesomeIcon.Times))
+ itemToRemove = quest;
+ }
+ }
+
+ if (itemToRemove != null)
+ {
+ Configuration.Stop.QuestsToStopAfter.Remove(itemToRemove.Id);
+ Save();
+ }
+ }
+ }
+}
private readonly PluginConfigComponent _pluginConfigComponent;
private readonly DutyConfigComponent _dutyConfigComponent;
private readonly SinglePlayerDutyConfigComponent _singlePlayerDutyConfigComponent;
+ private readonly StopConditionComponent _stopConditionComponent;
private readonly NotificationConfigComponent _notificationConfigComponent;
private readonly DebugConfigComponent _debugConfigComponent;
private readonly Configuration _configuration;
PluginConfigComponent pluginConfigComponent,
DutyConfigComponent dutyConfigComponent,
SinglePlayerDutyConfigComponent singlePlayerDutyConfigComponent,
+ StopConditionComponent stopConditionComponent,
NotificationConfigComponent notificationConfigComponent,
DebugConfigComponent debugConfigComponent,
Configuration configuration)
_pluginConfigComponent = pluginConfigComponent;
_dutyConfigComponent = dutyConfigComponent;
_singlePlayerDutyConfigComponent = singlePlayerDutyConfigComponent;
+ _stopConditionComponent = stopConditionComponent;
_notificationConfigComponent = notificationConfigComponent;
_debugConfigComponent = debugConfigComponent;
_configuration = configuration;
_pluginConfigComponent.DrawTab();
_dutyConfigComponent.DrawTab();
_singlePlayerDutyConfigComponent.DrawTab();
+ _stopConditionComponent.DrawTab();
_notificationConfigComponent.DrawTab();
_debugConfigComponent.DrawTab();
}
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
+using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using LLib.ImGui;
using Questionable.Model;
using Questionable.Model.Questing;
using Questionable.Windows.QuestComponents;
+using Questionable.Windows.Utils;
namespace Questionable.Windows;
private const char ClipboardSeparator = ';';
private readonly QuestController _questController;
- private readonly QuestRegistry _questRegistry;
private readonly QuestFunctions _questFunctions;
+ private readonly QuestSelector _questSelector;
private readonly QuestTooltipComponent _questTooltipComponent;
private readonly UiUtils _uiUtils;
private readonly IChatGui _chatGui;
private readonly IDalamudPluginInterface _pluginInterface;
- private string _searchString = string.Empty;
private ElementId? _draggedItem;
- public PriorityWindow(QuestController questController, QuestRegistry questRegistry, QuestFunctions questFunctions,
+ public PriorityWindow(QuestController questController, QuestFunctions questFunctions, QuestSelector questSelector,
QuestTooltipComponent questTooltipComponent, UiUtils uiUtils, IChatGui chatGui,
IDalamudPluginInterface pluginInterface)
: base("Quest Priority###QuestionableQuestPriority")
{
_questController = questController;
- _questRegistry = questRegistry;
_questFunctions = questFunctions;
+ _questSelector = questSelector;
_questTooltipComponent = questTooltipComponent;
_uiUtils = uiUtils;
_chatGui = chatGui;
_pluginInterface = pluginInterface;
+ _questSelector.SuggestionPredicate = quest =>
+ !quest.Info.IsMainScenarioQuest &&
+ !questFunctions.IsQuestUnobtainable(quest.Id) &&
+ questController.ManualPriorityQuests.All(x => x.Id != quest.Id);
+ _questSelector.DefaultPredicate = quest => questFunctions.IsQuestAccepted(quest.Id);
+ _questSelector.QuestSelected = quest => _questController.ManualPriorityQuests.Add(quest);
+
Size = new Vector2(400, 400);
SizeCondition = ImGuiCond.Once;
SizeConstraints = new WindowSizeConstraints
public override void DrawContent()
{
ImGui.Text("Quests to do first:");
- DrawQuestFilter();
+ _questSelector.DrawSelection();
DrawQuestList();
List<ElementId> clipboardItems = ParseClipboardItems();
"If you don't have any active MSQ quest, it will always try to pick up the next quest in the MSQ first.");
}
- private void DrawQuestFilter()
- {
- ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
- if (ImGui.BeginCombo($"##QuestSelection", "Add Quest...", ImGuiComboFlags.HeightLarge))
- {
- ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
- bool addFirst = ImGui.InputTextWithHint("", "Filter...", ref _searchString, 256,
- ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.EnterReturnsTrue);
-
- IEnumerable<Quest> foundQuests;
- if (!string.IsNullOrEmpty(_searchString))
- {
- bool DefaultPredicate(Quest x) => x.Info.Name.Contains(_searchString, StringComparison.CurrentCultureIgnoreCase);
-
- Func<Quest, bool> searchPredicate;
- if (ElementId.TryFromString(_searchString, out ElementId? elementId))
- searchPredicate = x => DefaultPredicate(x) || x.Id == elementId;
- else
- searchPredicate = DefaultPredicate;
-
- foundQuests = _questRegistry.AllQuests
- .Where(x => x.Id is not SatisfactionSupplyNpcId and not AlliedSocietyDailyId)
- .Where(searchPredicate)
- .Where(x => !_questFunctions.IsQuestUnobtainable(x.Id));
- }
- else
- {
- foundQuests = _questRegistry.AllQuests.Where(x => _questFunctions.IsQuestAccepted(x.Id));
- }
-
- foreach (var quest in foundQuests)
- {
- if (quest.Info.IsMainScenarioQuest || _questController.ManualPriorityQuests.Any(x => x.Id == quest.Id))
- continue;
-
- bool addThis = ImGui.Selectable(quest.Info.Name);
- if (addThis || addFirst)
- {
- _questController.ManualPriorityQuests.Add(quest);
-
- if (addFirst)
- {
- ImGui.CloseCurrentPopup();
- addFirst = false;
- }
- }
- }
-
- ImGui.EndCombo();
- }
-
- ImGui.Spacing();
- }
-
private void DrawQuestList()
{
List<Quest> priorityQuests = _questController.ManualPriorityQuests;
Vector2 topLeft = ImGui.GetCursorScreenPos() +
new Vector2(0, -ImGui.GetStyle().ItemSpacing.Y / 2);
var quest = priorityQuests[i];
- ImGui.PushID($"Quest{quest.Id}");
-
- var style = _uiUtils.GetQuestStyle(quest.Id);
- bool hovered;
- using (var _ = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
+ using (ImRaii.PushId($"Quest{quest.Id}"))
{
- ImGui.AlignTextToFramePadding();
- ImGui.TextColored(style.Color, style.Icon.ToIconString());
- hovered = ImGui.IsItemHovered();
- }
+ var style = _uiUtils.GetQuestStyle(quest.Id);
+ bool hovered;
+ using (var _ = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
+ {
+ ImGui.AlignTextToFramePadding();
+ ImGui.TextColored(style.Color, style.Icon.ToIconString());
+ hovered = ImGui.IsItemHovered();
+ }
- ImGui.SameLine();
- ImGui.AlignTextToFramePadding();
- ImGui.Text(quest.Info.Name);
- hovered |= ImGui.IsItemHovered();
+ ImGui.SameLine();
+ ImGui.AlignTextToFramePadding();
+ ImGui.Text(quest.Info.Name);
+ hovered |= ImGui.IsItemHovered();
- if (hovered)
- _questTooltipComponent.Draw(quest.Info);
+ if (hovered)
+ _questTooltipComponent.Draw(quest.Info);
- if (priorityQuests.Count > 1)
- {
- ImGui.PushFont(UiBuilder.IconFont);
- ImGui.SameLine(ImGui.GetContentRegionAvail().X +
- ImGui.GetStyle().WindowPadding.X -
- ImGui.CalcTextSize(FontAwesomeIcon.ArrowsUpDown.ToIconString()).X -
- ImGui.CalcTextSize(FontAwesomeIcon.Times.ToIconString()).X -
- ImGui.GetStyle().FramePadding.X * 4 -
- ImGui.GetStyle().ItemSpacing.X);
- ImGui.PopFont();
-
- if (_draggedItem == quest.Id)
+ if (priorityQuests.Count > 1)
{
- ImGuiComponents.IconButton("##Move", FontAwesomeIcon.ArrowsUpDown,
- ImGui.ColorConvertU32ToFloat4(ImGui.GetColorU32(ImGuiCol.ButtonActive)));
- }
- else
- ImGuiComponents.IconButton("##Move", FontAwesomeIcon.ArrowsUpDown);
+ using (ImRaii.PushFont(UiBuilder.IconFont))
+ {
+ ImGui.SameLine(ImGui.GetContentRegionAvail().X +
+ ImGui.GetStyle().WindowPadding.X -
+ ImGui.CalcTextSize(FontAwesomeIcon.ArrowsUpDown.ToIconString()).X -
+ ImGui.CalcTextSize(FontAwesomeIcon.Times.ToIconString()).X -
+ ImGui.GetStyle().FramePadding.X * 4 -
+ ImGui.GetStyle().ItemSpacing.X);
+ }
- if (_draggedItem == null && ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
- _draggedItem = quest.Id;
+ if (_draggedItem == quest.Id)
+ {
+ ImGuiComponents.IconButton("##Move", FontAwesomeIcon.ArrowsUpDown,
+ ImGui.ColorConvertU32ToFloat4(ImGui.GetColorU32(ImGuiCol.ButtonActive)));
+ }
+ else
+ ImGuiComponents.IconButton("##Move", FontAwesomeIcon.ArrowsUpDown);
- ImGui.SameLine();
- }
- else
- {
- ImGui.PushFont(UiBuilder.IconFont);
- ImGui.SameLine(ImGui.GetContentRegionAvail().X +
- ImGui.GetStyle().WindowPadding.X -
- ImGui.CalcTextSize(FontAwesomeIcon.Times.ToIconString()).X -
- ImGui.GetStyle().FramePadding.X * 2);
- ImGui.PopFont();
- }
+ if (_draggedItem == null && ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
+ _draggedItem = quest.Id;
- if (ImGuiComponents.IconButton($"##Remove{i}", FontAwesomeIcon.Times))
- itemToRemove = quest;
+ ImGui.SameLine();
+ }
+ else
+ {
+ using (ImRaii.PushFont(UiBuilder.IconFont))
+ {
+ ImGui.SameLine(ImGui.GetContentRegionAvail().X +
+ ImGui.GetStyle().WindowPadding.X -
+ ImGui.CalcTextSize(FontAwesomeIcon.Times.ToIconString()).X -
+ ImGui.GetStyle().FramePadding.X * 2);
+ }
+ }
- ImGui.PopID();
+ if (ImGuiComponents.IconButton($"##Remove{i}", FontAwesomeIcon.Times))
+ itemToRemove = quest;
+ }
Vector2 bottomRight = new Vector2(topLeft.X + width,
ImGui.GetCursorScreenPos().Y - ImGui.GetStyle().ItemSpacing.Y + 2);
private readonly Configuration _configuration;
private readonly QuestRegistry _questRegistry;
private readonly PriorityWindow _priorityWindow;
+ private readonly UiUtils _uiUtils;
private readonly IChatGui _chatGui;
private readonly ILogger<ActiveQuestComponent> _logger;
Configuration configuration,
QuestRegistry questRegistry,
PriorityWindow priorityWindow,
+ UiUtils uiUtils,
IChatGui chatGui,
ILogger<ActiveQuestComponent> logger)
{
_configuration = configuration;
_questRegistry = questRegistry;
_priorityWindow = priorityWindow;
+ _uiUtils = uiUtils;
_chatGui = chatGui;
_logger = logger;
}
ImGui.TextColored(ImGuiColors.DalamudRed, "Disabled");
}
+ if (_configuration.Stop.Enabled &&
+ _configuration.Stop.QuestsToStopAfter.Any(x => !_questFunctions.IsQuestComplete(x) && !_questFunctions.IsQuestUnobtainable(x)))
+ {
+ ImGui.SameLine();
+ ImGui.TextColored(ImGuiColors.ParsedPurple, SeIconChar.Clock.ToIconString());
+ if (ImGui.IsItemHovered())
+ {
+ using var tooltip = ImRaii.Tooltip();
+ if (tooltip)
+ {
+ ImGui.Text("Questionable will stop after completing any of the following quests:");
+ foreach (var questId in _configuration.Stop.QuestsToStopAfter)
+ {
+ if (_questRegistry.TryGetQuest(questId, out var quest))
+ {
+ (Vector4 color, FontAwesomeIcon icon, _) = _uiUtils.GetQuestStyle(questId);
+ _uiUtils.ChecklistItem($"{quest.Info.Name} ({questId})", color, icon);
+ }
+ }
+ }
+ }
+ }
+
if (_configuration.Advanced.AdditionalStatusInformation && _questController.IsInterruptible())
{
ImGui.SameLine();
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Dalamud.Bindings.ImGui;
+using Questionable.Controller;
+using Questionable.Functions;
+using Questionable.Model;
+using Questionable.Model.Questing;
+
+namespace Questionable.Windows.Utils;
+
+internal sealed class QuestSelector(QuestRegistry questRegistry, QuestFunctions questFunctions)
+{
+ private string _searchString = string.Empty;
+
+ public Predicate<Quest>? SuggestionPredicate { private get; set; }
+ public Predicate<Quest>? DefaultPredicate { private get; set; }
+ public Action<Quest>? QuestSelected { private get; set; }
+
+ public void DrawSelection()
+ {
+ if (QuestSelected == null)
+ throw new InvalidOperationException("QuestSelected action must be set before drawing the quest selector.");
+
+ ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
+ if (ImGui.BeginCombo("##QuestSelection", "Add Quest...", ImGuiComboFlags.HeightLarge))
+ {
+ ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
+ bool addFirst = ImGui.InputTextWithHint("", "Filter...", ref _searchString, 256,
+ ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.EnterReturnsTrue);
+
+ IEnumerable<Quest> foundQuests;
+ if (!string.IsNullOrEmpty(_searchString))
+ {
+ bool DefaultPredicate(Quest x) =>
+ x.Info.Name.Contains(_searchString, StringComparison.CurrentCultureIgnoreCase);
+
+ Func<Quest, bool> searchPredicate;
+ if (ElementId.TryFromString(_searchString, out ElementId? elementId))
+ searchPredicate = x => DefaultPredicate(x) || x.Id == elementId;
+ else
+ searchPredicate = DefaultPredicate;
+
+ foundQuests = questRegistry.AllQuests
+ .Where(x => x.Id is not SatisfactionSupplyNpcId and not AlliedSocietyDailyId)
+ .Where(searchPredicate);
+ }
+ else
+ {
+ foundQuests = questRegistry.AllQuests.Where(x => DefaultPredicate?.Invoke(x) ?? true);
+ }
+
+ foreach (var quest in foundQuests)
+ {
+ if (SuggestionPredicate != null && !SuggestionPredicate.Invoke(quest))
+ continue;
+
+ bool addThis = ImGui.Selectable(quest.Info.Name);
+ if (addThis || addFirst)
+ {
+ QuestSelected(quest);
+
+ if (addFirst)
+ {
+ ImGui.CloseCurrentPopup();
+ addFirst = false;
+ }
+ }
+ }
+
+ ImGui.EndCombo();
+ }
+
+ ImGui.Spacing();
+ }
+}