using Questionable.Validation;
using Questionable.Validation.Validators;
using Questionable.Windows;
+using Questionable.Windows.ConfigComponents;
using Questionable.Windows.JournalComponents;
using Questionable.Windows.QuestComponents;
using Action = Questionable.Controller.Steps.Interactions.Action;
serviceCollection.AddSingleton<QuestValidationWindow>();
serviceCollection.AddSingleton<JournalProgressWindow>();
serviceCollection.AddSingleton<PriorityWindow>();
+
+ serviceCollection.AddSingleton<GeneralConfigComponent>();
+ serviceCollection.AddSingleton<DutyConfigComponent>();
+ serviceCollection.AddSingleton<NotificationConfigComponent>();
+ serviceCollection.AddSingleton<DebugConfigComponent>();
}
private static void AddQuestValidators(ServiceCollection serviceCollection)
--- /dev/null
+using System.Text;
+using Dalamud.Game.Text;
+using Dalamud.Plugin;
+using ImGuiNET;
+
+namespace Questionable.Windows.ConfigComponents;
+
+internal abstract class ConfigComponent
+{
+ protected const string DutyClipboardSeparator = ";";
+ protected const string DutyWhitelistPrefix = "+";
+ protected const string DutyBlacklistPrefix = "-";
+
+ protected readonly string[] SupportedCfcOptions =
+ [
+ $"{SeIconChar.Circle.ToIconChar()} Enabled (Default)",
+ $"{SeIconChar.Circle.ToIconChar()} Enabled",
+ $"{SeIconChar.Cross.ToIconChar()} Disabled"
+ ];
+
+ protected readonly string[] UnsupportedCfcOptions =
+ [
+ $"{SeIconChar.Cross.ToIconChar()} Disabled (Default)",
+ $"{SeIconChar.Circle.ToIconChar()} Enabled",
+ $"{SeIconChar.Cross.ToIconChar()} Disabled"
+ ];
+
+ private readonly IDalamudPluginInterface _pluginInterface;
+
+ protected ConfigComponent(IDalamudPluginInterface pluginInterface, Configuration configuration)
+ {
+ _pluginInterface = pluginInterface;
+ Configuration = configuration;
+ }
+
+ protected Configuration Configuration { get; }
+
+ public abstract void DrawTab();
+
+ protected void Save() => _pluginInterface.SavePluginConfig(Configuration);
+
+ protected static string FormatLevel(int level)
+ {
+ if (level == 0)
+ return string.Empty;
+
+ return $"{FormatLevel(level / 10)}{(SeIconChar.Number0 + level % 10).ToIconChar()}";
+ }
+
+ /// <summary>
+ /// The default implementation for <see cref="ImGui.GetClipboardText"/> throws an NullReferenceException if the clipboard is empty, maybe also if it doesn't contain text.
+ /// </summary>
+ protected unsafe string? GetClipboardText()
+ {
+ byte* ptr = ImGuiNative.igGetClipboardText();
+ if (ptr == null)
+ return null;
+
+ int byteCount = 0;
+ while (ptr[byteCount] != 0)
+ ++byteCount;
+ return Encoding.UTF8.GetString(ptr, byteCount);
+ }
+}
--- /dev/null
+using Dalamud.Interface.Colors;
+using Dalamud.Interface.Utility.Raii;
+using Dalamud.Plugin;
+using ImGuiNET;
+
+namespace Questionable.Windows.ConfigComponents;
+
+internal sealed class DebugConfigComponent : ConfigComponent
+{
+ public DebugConfigComponent(IDalamudPluginInterface pluginInterface, Configuration configuration)
+ : base(pluginInterface, configuration)
+ {
+ }
+
+ public override void DrawTab()
+ {
+ using var tab = ImRaii.TabItem("Advanced");
+ if (!tab)
+ return;
+
+ ImGui.TextColored(ImGuiColors.DalamudRed,
+ "Enabling any option here may cause unexpected behavior. Use at your own risk.");
+
+ ImGui.Separator();
+
+ bool debugOverlay = Configuration.Advanced.DebugOverlay;
+ if (ImGui.Checkbox("Enable debug overlay", ref debugOverlay))
+ {
+ Configuration.Advanced.DebugOverlay = debugOverlay;
+ Save();
+ }
+
+ bool neverFly = Configuration.Advanced.NeverFly;
+ if (ImGui.Checkbox("Disable flying (even if unlocked for the zone)", ref neverFly))
+ {
+ Configuration.Advanced.NeverFly = neverFly;
+ Save();
+ }
+
+ bool additionalStatusInformation = Configuration.Advanced.AdditionalStatusInformation;
+ if (ImGui.Checkbox("Draw additional status information", ref additionalStatusInformation))
+ {
+ Configuration.Advanced.AdditionalStatusInformation = additionalStatusInformation;
+ Save();
+ }
+
+ ImGui.EndTabItem();
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+using Dalamud.Game.Text;
+using Dalamud.Interface;
+using Dalamud.Interface.Colors;
+using Dalamud.Interface.Components;
+using Dalamud.Interface.Utility.Raii;
+using Dalamud.Plugin;
+using Dalamud.Plugin.Services;
+using Dalamud.Utility;
+using ImGuiNET;
+using Lumina.Excel.Sheets;
+using Questionable.Controller;
+using Questionable.Data;
+using Questionable.External;
+using Questionable.Model;
+
+namespace Questionable.Windows.ConfigComponents;
+
+internal sealed class DutyConfigComponent : ConfigComponent
+{
+ private const string DutyClipboardPrefix = "qst:duty:";
+
+ private readonly QuestRegistry _questRegistry;
+ private readonly AutoDutyIpc _autoDutyIpc;
+ private readonly Dictionary<EExpansionVersion, List<DutyInfo>> _contentFinderConditionNames;
+
+ public DutyConfigComponent(
+ IDalamudPluginInterface pluginInterface,
+ Configuration configuration,
+ IDataManager dataManager,
+ QuestRegistry questRegistry,
+ AutoDutyIpc autoDutyIpc,
+ TerritoryData territoryData)
+ : base(pluginInterface, configuration)
+ {
+ _questRegistry = questRegistry;
+ _autoDutyIpc = autoDutyIpc;
+
+ _contentFinderConditionNames = dataManager.GetExcelSheet<DawnContent>()
+ .Where(x => x is { RowId: > 0, Unknown16: false })
+ .OrderBy(x => x.Unknown15) // SortKey for the support UI
+ .Select(x => x.Content.ValueNullable)
+ .Where(x => x != null)
+ .Select(x => x!.Value)
+ .Select(x => new
+ {
+ Expansion = (EExpansionVersion)x.TerritoryType.Value.ExVersion.RowId,
+ CfcId = x.RowId,
+ Name = territoryData.GetContentFinderCondition(x.RowId)?.Name ?? "?",
+ TerritoryId = x.TerritoryType.RowId,
+ ContentType = x.ContentType.RowId,
+ Level = x.ClassJobLevelRequired,
+ x.SortKey
+ })
+ .GroupBy(x => x.Expansion)
+ .ToDictionary(x => x.Key,
+ x => x
+ .Select(y => new DutyInfo(y.CfcId, y.TerritoryId,
+ $"{SeIconChar.LevelEn.ToIconChar()}{FormatLevel(y.Level)} {y.Name}"))
+ .ToList());
+ }
+
+ public override void DrawTab()
+ {
+ using var tab = ImRaii.TabItem("Duties");
+ if (!tab)
+ return;
+
+ bool runInstancedContentWithAutoDuty = Configuration.Duties.RunInstancedContentWithAutoDuty;
+ if (ImGui.Checkbox("Run instanced content with AutoDuty and BossMod", ref runInstancedContentWithAutoDuty))
+ {
+ Configuration.Duties.RunInstancedContentWithAutoDuty = runInstancedContentWithAutoDuty;
+ Save();
+ }
+
+ ImGui.SameLine();
+ ImGuiComponents.HelpMarker(
+ "The combat module used for this is configured by AutoDuty, ignoring whichever selection you've made in Questionable's \"General\" configuration.");
+
+ ImGui.Separator();
+
+ using (ImRaii.Disabled(!runInstancedContentWithAutoDuty))
+ {
+ ImGui.Text(
+ "Questionable includes a default list of duties that work if AutoDuty and BossMod are installed.");
+
+ ImGui.Text(
+ "The included list of duties can change with each update, and is based on the following spreadsheet:");
+ if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.GlobeEurope, "Open AutoDuty spreadsheet"))
+ Util.OpenLink(
+ "https://docs.google.com/spreadsheets/d/151RlpqRcCpiD_VbQn6Duf-u-S71EP7d0mx3j1PDNoNA/edit?pli=1#gid=0");
+
+ ImGui.Separator();
+ ImGui.Text("You can override the dungeon settings for each individual dungeon/trial:");
+
+ DrawConfigTable(runInstancedContentWithAutoDuty);
+ DrawClipboardButtons();
+
+ ImGui.SameLine();
+
+ using (var unused = ImRaii.Disabled(!ImGui.IsKeyDown(ImGuiKey.ModCtrl)))
+ {
+ if (ImGui.Button("Reset to default"))
+ {
+ Configuration.Duties.WhitelistedDutyCfcIds.Clear();
+ Configuration.Duties.BlacklistedDutyCfcIds.Clear();
+ Save();
+ }
+ }
+
+ if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
+ ImGui.SetTooltip("Hold CTRL to enable this button.");
+ }
+ }
+
+ private void DrawConfigTable(bool runInstancedContentWithAutoDuty)
+ {
+ using var child = ImRaii.Child("DutyConfiguration", new Vector2(-1, 400), true);
+ if (!child)
+ return;
+
+ foreach (EExpansionVersion expansion in Enum.GetValues<EExpansionVersion>())
+ {
+ if (ImGui.CollapsingHeader(expansion.ToString()))
+ {
+ using var table = ImRaii.Table($"Duties{expansion}", 2, ImGuiTableFlags.SizingFixedFit);
+ if (table)
+ {
+ ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch);
+ ImGui.TableSetupColumn("Options", ImGuiTableColumnFlags.WidthFixed, 200f);
+
+ if (_contentFinderConditionNames.TryGetValue(expansion, out var cfcNames))
+ {
+ foreach (var (cfcId, territoryId, name) in cfcNames)
+ {
+ if (_questRegistry.TryGetDutyByContentFinderConditionId(cfcId,
+ out bool autoDutyEnabledByDefault))
+ {
+ ImGui.TableNextRow();
+
+ string[] labels = autoDutyEnabledByDefault
+ ? SupportedCfcOptions
+ : UnsupportedCfcOptions;
+ int value = 0;
+ if (Configuration.Duties.WhitelistedDutyCfcIds.Contains(cfcId))
+ value = 1;
+ if (Configuration.Duties.BlacklistedDutyCfcIds.Contains(cfcId))
+ value = 2;
+
+ if (ImGui.TableNextColumn())
+ {
+ ImGui.AlignTextToFramePadding();
+ ImGui.TextUnformatted(name);
+ if (ImGui.IsItemHovered() &&
+ Configuration.Advanced.AdditionalStatusInformation)
+ {
+ using var tooltip = ImRaii.Tooltip();
+ if (tooltip)
+ {
+ ImGui.TextUnformatted(name);
+ ImGui.Separator();
+ ImGui.BulletText($"TerritoryId: {territoryId}");
+ ImGui.BulletText($"ContentFinderConditionId: {cfcId}");
+ }
+ }
+
+ if (runInstancedContentWithAutoDuty && !_autoDutyIpc.HasPath(cfcId))
+ ImGuiComponents.HelpMarker("This duty is not supported by AutoDuty",
+ FontAwesomeIcon.Times, ImGuiColors.DalamudRed);
+ }
+
+ if (ImGui.TableNextColumn())
+ {
+ using var _ = ImRaii.PushId($"##Dungeon{cfcId}");
+ ImGui.SetNextItemWidth(200);
+ if (ImGui.Combo(string.Empty, ref value, labels, labels.Length))
+ {
+ Configuration.Duties.WhitelistedDutyCfcIds.Remove(cfcId);
+ Configuration.Duties.BlacklistedDutyCfcIds.Remove(cfcId);
+
+ if (value == 1)
+ Configuration.Duties.WhitelistedDutyCfcIds.Add(cfcId);
+ else if (value == 2)
+ Configuration.Duties.BlacklistedDutyCfcIds.Add(cfcId);
+
+ Save();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void DrawClipboardButtons()
+ {
+ using (ImRaii.Disabled(Configuration.Duties.WhitelistedDutyCfcIds.Count +
+ Configuration.Duties.BlacklistedDutyCfcIds.Count == 0))
+ {
+ if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Copy, "Export to clipboard"))
+ {
+ var whitelisted =
+ Configuration.Duties.WhitelistedDutyCfcIds.Select(x => $"{DutyWhitelistPrefix}{x}");
+ var blacklisted =
+ Configuration.Duties.BlacklistedDutyCfcIds.Select(x => $"{DutyBlacklistPrefix}{x}");
+ string text = DutyClipboardPrefix + Convert.ToBase64String(Encoding.UTF8.GetBytes(
+ string.Join(DutyClipboardSeparator, whitelisted.Concat(blacklisted))));
+ ImGui.SetClipboardText(text);
+ }
+ }
+
+ ImGui.SameLine();
+
+ string? clipboardText = GetClipboardText();
+ using (ImRaii.Disabled(clipboardText == null ||
+ !clipboardText.StartsWith(DutyClipboardPrefix, StringComparison.InvariantCulture)))
+ {
+ if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Paste, "Import from Clipboard"))
+ {
+ clipboardText = clipboardText!.Substring(DutyClipboardPrefix.Length);
+ string text = Encoding.UTF8.GetString(Convert.FromBase64String(clipboardText));
+
+ Configuration.Duties.WhitelistedDutyCfcIds.Clear();
+ Configuration.Duties.BlacklistedDutyCfcIds.Clear();
+ foreach (string part in text.Split(DutyClipboardSeparator))
+ {
+ if (part.StartsWith(DutyWhitelistPrefix, StringComparison.InvariantCulture) &&
+ uint.TryParse(part.AsSpan(DutyWhitelistPrefix.Length), CultureInfo.InvariantCulture,
+ out uint whitelistedCfcId))
+ Configuration.Duties.WhitelistedDutyCfcIds.Add(whitelistedCfcId);
+
+ if (part.StartsWith(DutyBlacklistPrefix, StringComparison.InvariantCulture) &&
+ uint.TryParse(part.AsSpan(DutyBlacklistPrefix.Length), CultureInfo.InvariantCulture,
+ out uint blacklistedCfcId))
+ Configuration.Duties.WhitelistedDutyCfcIds.Add(blacklistedCfcId);
+ }
+ }
+ }
+ }
+
+ private sealed record DutyInfo(uint CfcId, uint TerritoryId, string Name);
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Dalamud.Interface.Utility.Raii;
+using Dalamud.Plugin;
+using Dalamud.Plugin.Services;
+using ImGuiNET;
+using Lumina.Excel.Sheets;
+using Questionable.Controller;
+using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany;
+
+namespace Questionable.Windows.ConfigComponents;
+
+internal sealed class GeneralConfigComponent : ConfigComponent
+{
+ private static readonly List<(uint Id, string Name)> DefaultMounts = [(0, "Mount Roulette")];
+
+ private readonly CombatController _combatController;
+
+ private readonly uint[] _mountIds;
+ private readonly string[] _mountNames;
+ private readonly string[] _combatModuleNames = ["None", "Boss Mod (VBM)", "Wrath Combo", "Rotation Solver Reborn"];
+
+ private readonly string[] _grandCompanyNames =
+ ["None (manually pick quest)", "Maelstrom", "Twin Adder", "Immortal Flames"];
+
+ public GeneralConfigComponent(
+ IDalamudPluginInterface pluginInterface,
+ Configuration configuration,
+ CombatController combatController,
+ IDataManager dataManager)
+ : base(pluginInterface, configuration)
+ {
+ _combatController = combatController;
+
+ var mounts = dataManager.GetExcelSheet<Mount>()
+ .Where(x => x is { RowId: > 0, Icon: > 0 })
+ .Select(x => (MountId: x.RowId, Name: x.Singular.ToString()))
+ .Where(x => !string.IsNullOrEmpty(x.Name))
+ .OrderBy(x => x.Name)
+ .ToList();
+ _mountIds = DefaultMounts.Select(x => x.Id).Concat(mounts.Select(x => x.MountId)).ToArray();
+ _mountNames = DefaultMounts.Select(x => x.Name).Concat(mounts.Select(x => x.Name)).ToArray();
+ }
+
+ public override void DrawTab()
+ {
+ using var tab = ImRaii.TabItem("General");
+ if (!tab)
+ return;
+
+ using (ImRaii.Disabled(_combatController.IsRunning))
+ {
+ int selectedCombatModule = (int)Configuration.General.CombatModule;
+ if (ImGui.Combo("Preferred Combat Module", ref selectedCombatModule, _combatModuleNames,
+ _combatModuleNames.Length))
+ {
+ Configuration.General.CombatModule = (Configuration.ECombatModule)selectedCombatModule;
+ Save();
+ }
+ }
+
+ int selectedMount = Array.FindIndex(_mountIds, x => x == Configuration.General.MountId);
+ if (selectedMount == -1)
+ {
+ selectedMount = 0;
+ Configuration.General.MountId = _mountIds[selectedMount];
+ Save();
+ }
+
+ if (ImGui.Combo("Preferred Mount", ref selectedMount, _mountNames, _mountNames.Length))
+ {
+ Configuration.General.MountId = _mountIds[selectedMount];
+ Save();
+ }
+
+ int grandCompany = (int)Configuration.General.GrandCompany;
+ if (ImGui.Combo("Preferred Grand Company", ref grandCompany, _grandCompanyNames,
+ _grandCompanyNames.Length))
+ {
+ Configuration.General.GrandCompany = (GrandCompany)grandCompany;
+ Save();
+ }
+
+ bool hideInAllInstances = Configuration.General.HideInAllInstances;
+ if (ImGui.Checkbox("Hide quest window in all instanced duties", ref hideInAllInstances))
+ {
+ Configuration.General.HideInAllInstances = hideInAllInstances;
+ Save();
+ }
+
+ bool useEscToCancelQuesting = Configuration.General.UseEscToCancelQuesting;
+ if (ImGui.Checkbox("Use ESC to cancel questing/movement", ref useEscToCancelQuesting))
+ {
+ Configuration.General.UseEscToCancelQuesting = useEscToCancelQuesting;
+ Save();
+ }
+
+ bool showIncompleteSeasonalEvents = Configuration.General.ShowIncompleteSeasonalEvents;
+ if (ImGui.Checkbox("Show details for incomplete seasonal events", ref showIncompleteSeasonalEvents))
+ {
+ Configuration.General.ShowIncompleteSeasonalEvents = showIncompleteSeasonalEvents;
+ Save();
+ }
+
+ bool configureTextAdvance = Configuration.General.ConfigureTextAdvance;
+ if (ImGui.Checkbox("Automatically configure TextAdvance with the recommended settings",
+ ref configureTextAdvance))
+ {
+ Configuration.General.ConfigureTextAdvance = configureTextAdvance;
+ Save();
+ }
+ }
+}
--- /dev/null
+using System;
+using System.Linq;
+using Dalamud.Game.Text;
+using Dalamud.Interface.Components;
+using Dalamud.Interface.Utility.Raii;
+using Dalamud.Plugin;
+using Dalamud.Utility;
+using ImGuiNET;
+using Questionable.External;
+
+namespace Questionable.Windows.ConfigComponents;
+
+internal sealed class NotificationConfigComponent : ConfigComponent
+{
+ private readonly NotificationMasterIpc _notificationMasterIpc;
+
+ public NotificationConfigComponent(
+ IDalamudPluginInterface pluginInterface,
+ Configuration configuration,
+ NotificationMasterIpc notificationMasterIpc)
+ : base(pluginInterface, configuration)
+ {
+ _notificationMasterIpc = notificationMasterIpc;
+ }
+
+ public override void DrawTab()
+ {
+ using var tab = ImRaii.TabItem("Notifications");
+ if (!tab)
+ return;
+
+ bool enabled = Configuration.Notifications.Enabled;
+ if (ImGui.Checkbox("Enable notifications when manual interaction is required", ref enabled))
+ {
+ Configuration.Notifications.Enabled = enabled;
+ Save();
+ }
+
+ using (ImRaii.Disabled(!Configuration.Notifications.Enabled))
+ {
+ using (ImRaii.PushIndent())
+ {
+ var xivChatTypes = Enum.GetValues<XivChatType>()
+ .Where(x => x != XivChatType.StandardEmote)
+ .ToArray();
+ var selectedChatType = Array.IndexOf(xivChatTypes, Configuration.Notifications.ChatType);
+ string[] chatTypeNames = xivChatTypes
+ .Select(t => t.GetAttribute<XivChatTypeInfoAttribute>()?.FancyName ?? t.ToString())
+ .ToArray();
+ if (ImGui.Combo("Chat channel", ref selectedChatType, chatTypeNames,
+ chatTypeNames.Length))
+ {
+ Configuration.Notifications.ChatType = xivChatTypes[selectedChatType];
+ Save();
+ }
+
+ ImGui.Separator();
+ ImGui.Text("NotificationMaster settings");
+ ImGui.SameLine();
+ ImGuiComponents.HelpMarker("Requires the plugin 'NotificationMaster' to be installed.");
+ using (ImRaii.Disabled(!_notificationMasterIpc.Enabled))
+ {
+ bool showTrayMessage = Configuration.Notifications.ShowTrayMessage;
+ if (ImGui.Checkbox("Show tray notification", ref showTrayMessage))
+ {
+ Configuration.Notifications.ShowTrayMessage = showTrayMessage;
+ Save();
+ }
+
+ bool flashTaskbar = Configuration.Notifications.FlashTaskbar;
+ if (ImGui.Checkbox("Flash taskbar icon", ref flashTaskbar))
+ {
+ Configuration.Notifications.FlashTaskbar = flashTaskbar;
+ Save();
+ }
+ }
+ }
+ }
+ }
+}
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Numerics;
-using System.Text;
-using Dalamud.Game.Text;
-using Dalamud.Interface;
-using Dalamud.Interface.Colors;
-using Dalamud.Interface.Components;
-using Dalamud.Interface.Utility.Raii;
+using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin;
-using Dalamud.Plugin.Services;
-using Dalamud.Utility;
using ImGuiNET;
using LLib.ImGui;
-using Lumina.Excel.Sheets;
-using Questionable.Controller;
-using Questionable.Data;
-using Questionable.External;
-using Questionable.Model;
-using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany;
+using Questionable.Windows.ConfigComponents;
namespace Questionable.Windows;
internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
{
- private const string DutyClipboardPrefix = "qst:duty:";
- private const string DutyClipboardSeparator = ";";
- private const string DutyWhitelistPrefix = "+";
- private const string DutyBlacklistPrefix = "-";
-
- private static readonly List<(uint Id, string Name)> DefaultMounts = [(0, "Mount Roulette")];
-
private readonly IDalamudPluginInterface _pluginInterface;
- private readonly NotificationMasterIpc _notificationMasterIpc;
+ private readonly GeneralConfigComponent _generalConfigComponent;
+ private readonly DutyConfigComponent _dutyConfigComponent;
+ private readonly NotificationConfigComponent _notificationConfigComponent;
+ private readonly DebugConfigComponent _debugConfigComponent;
private readonly Configuration _configuration;
- private readonly CombatController _combatController;
- private readonly QuestRegistry _questRegistry;
- private readonly AutoDutyIpc _autoDutyIpc;
-
- private readonly uint[] _mountIds;
- private readonly string[] _mountNames;
-
- private readonly string[] _combatModuleNames = ["None", "Boss Mod (VBM)", "Wrath Combo", "Rotation Solver Reborn"];
-
- private readonly string[] _grandCompanyNames =
- ["None (manually pick quest)", "Maelstrom", "Twin Adder", "Immortal Flames"];
- private readonly string[] _supportedCfcOptions =
- [
- $"{SeIconChar.Circle.ToIconChar()} Enabled (Default)",
- $"{SeIconChar.Circle.ToIconChar()} Enabled",
- $"{SeIconChar.Cross.ToIconChar()} Disabled"
- ];
-
- private readonly string[] _unsupportedCfcOptions =
- [
- $"{SeIconChar.Cross.ToIconChar()} Disabled (Default)",
- $"{SeIconChar.Circle.ToIconChar()} Enabled",
- $"{SeIconChar.Cross.ToIconChar()} Disabled"
- ];
-
- private readonly Dictionary<EExpansionVersion, List<DutyInfo>> _contentFinderConditionNames;
-
- public ConfigWindow(IDalamudPluginInterface pluginInterface,
- NotificationMasterIpc notificationMasterIpc,
- Configuration configuration,
- IDataManager dataManager,
- CombatController combatController,
- TerritoryData territoryData,
- QuestRegistry questRegistry,
- AutoDutyIpc autoDutyIpc)
+ public ConfigWindow(
+ IDalamudPluginInterface pluginInterface,
+ GeneralConfigComponent generalConfigComponent,
+ DutyConfigComponent dutyConfigComponent,
+ NotificationConfigComponent notificationConfigComponent,
+ DebugConfigComponent debugConfigComponent,
+ Configuration configuration)
: base("Config - Questionable###QuestionableConfig", ImGuiWindowFlags.AlwaysAutoResize)
{
_pluginInterface = pluginInterface;
- _notificationMasterIpc = notificationMasterIpc;
+ _generalConfigComponent = generalConfigComponent;
+ _dutyConfigComponent = dutyConfigComponent;
+ _notificationConfigComponent = notificationConfigComponent;
+ _debugConfigComponent = debugConfigComponent;
_configuration = configuration;
- _combatController = combatController;
- _questRegistry = questRegistry;
- _autoDutyIpc = autoDutyIpc;
-
- var mounts = dataManager.GetExcelSheet<Mount>()
- .Where(x => x is { RowId: > 0, Icon: > 0 })
- .Select(x => (MountId: x.RowId, Name: x.Singular.ToString()))
- .Where(x => !string.IsNullOrEmpty(x.Name))
- .OrderBy(x => x.Name)
- .ToList();
- _mountIds = DefaultMounts.Select(x => x.Id).Concat(mounts.Select(x => x.MountId)).ToArray();
- _mountNames = DefaultMounts.Select(x => x.Name).Concat(mounts.Select(x => x.Name)).ToArray();
-
- _contentFinderConditionNames = dataManager.GetExcelSheet<DawnContent>()
- .Where(x => x is { RowId: > 0, Unknown16: false })
- .OrderBy(x => x.Unknown15) // SortKey for the support UI
- .Select(x => x.Content.ValueNullable)
- .Where(x => x != null)
- .Select(x => x!.Value)
- .Select(x => new
- {
- Expansion = (EExpansionVersion)x.TerritoryType.Value.ExVersion.RowId,
- CfcId = x.RowId,
- Name = territoryData.GetContentFinderCondition(x.RowId)?.Name ?? "?",
- TerritoryId = x.TerritoryType.RowId,
- ContentType = x.ContentType.RowId,
- Level = x.ClassJobLevelRequired,
- x.SortKey
- })
- .GroupBy(x => x.Expansion)
- .ToDictionary(x => x.Key,
- x => x
- .Select(y => new DutyInfo(y.CfcId, y.TerritoryId, $"{SeIconChar.LevelEn.ToIconChar()}{FormatLevel(y.Level)} {y.Name}"))
- .ToList());
}
public WindowConfig WindowConfig => _configuration.ConfigWindowConfig;
- private static string FormatLevel(int level)
- {
- if (level == 0)
- return string.Empty;
-
- return $"{FormatLevel(level / 10)}{(SeIconChar.Number0 + level % 10).ToIconChar()}";
- }
-
public override void Draw()
{
using var tabBar = ImRaii.TabBar("QuestionableConfigTabs");
if (!tabBar)
return;
- DrawGeneralTab();
- DrawDutiesTab();
- DrawNotificationsTab();
- DrawAdvancedTab();
- }
-
- private void DrawGeneralTab()
- {
- using var tab = ImRaii.TabItem("General");
- if (!tab)
- return;
-
- using (ImRaii.Disabled(_combatController.IsRunning))
- {
- int selectedCombatModule = (int)_configuration.General.CombatModule;
- if (ImGui.Combo("Preferred Combat Module", ref selectedCombatModule, _combatModuleNames,
- _combatModuleNames.Length))
- {
- _configuration.General.CombatModule = (Configuration.ECombatModule)selectedCombatModule;
- Save();
- }
- }
-
- int selectedMount = Array.FindIndex(_mountIds, x => x == _configuration.General.MountId);
- if (selectedMount == -1)
- {
- selectedMount = 0;
- _configuration.General.MountId = _mountIds[selectedMount];
- Save();
- }
-
- if (ImGui.Combo("Preferred Mount", ref selectedMount, _mountNames, _mountNames.Length))
- {
- _configuration.General.MountId = _mountIds[selectedMount];
- Save();
- }
-
- int grandCompany = (int)_configuration.General.GrandCompany;
- if (ImGui.Combo("Preferred Grand Company", ref grandCompany, _grandCompanyNames,
- _grandCompanyNames.Length))
- {
- _configuration.General.GrandCompany = (GrandCompany)grandCompany;
- Save();
- }
-
- bool hideInAllInstances = _configuration.General.HideInAllInstances;
- if (ImGui.Checkbox("Hide quest window in all instanced duties", ref hideInAllInstances))
- {
- _configuration.General.HideInAllInstances = hideInAllInstances;
- Save();
- }
-
- bool useEscToCancelQuesting = _configuration.General.UseEscToCancelQuesting;
- if (ImGui.Checkbox("Use ESC to cancel questing/movement", ref useEscToCancelQuesting))
- {
- _configuration.General.UseEscToCancelQuesting = useEscToCancelQuesting;
- Save();
- }
-
- bool showIncompleteSeasonalEvents = _configuration.General.ShowIncompleteSeasonalEvents;
- if (ImGui.Checkbox("Show details for incomplete seasonal events", ref showIncompleteSeasonalEvents))
- {
- _configuration.General.ShowIncompleteSeasonalEvents = showIncompleteSeasonalEvents;
- Save();
- }
-
- bool configureTextAdvance = _configuration.General.ConfigureTextAdvance;
- if (ImGui.Checkbox("Automatically configure TextAdvance with the recommended settings",
- ref configureTextAdvance))
- {
- _configuration.General.ConfigureTextAdvance = configureTextAdvance;
- Save();
- }
- }
-
- private void DrawDutiesTab()
- {
- using var tab = ImRaii.TabItem("Duties");
- if (!tab)
- return;
-
- bool runInstancedContentWithAutoDuty = _configuration.Duties.RunInstancedContentWithAutoDuty;
- if (ImGui.Checkbox("Run instanced content with AutoDuty and BossMod", ref runInstancedContentWithAutoDuty))
- {
- _configuration.Duties.RunInstancedContentWithAutoDuty = runInstancedContentWithAutoDuty;
- Save();
- }
-
- ImGui.SameLine();
- ImGuiComponents.HelpMarker(
- "The combat module used for this is configured by AutoDuty, ignoring whichever selection you've made in Questionable's \"General\" configuration.");
-
- ImGui.Separator();
-
- using (ImRaii.Disabled(!runInstancedContentWithAutoDuty))
- {
- ImGui.Text(
- "Questionable includes a default list of duties that work if AutoDuty and BossMod are installed.");
-
- ImGui.Text("The included list of duties can change with each update, and is based on the following spreadsheet:");
- if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.GlobeEurope, "Open AutoDuty spreadsheet"))
- Util.OpenLink(
- "https://docs.google.com/spreadsheets/d/151RlpqRcCpiD_VbQn6Duf-u-S71EP7d0mx3j1PDNoNA/edit?pli=1#gid=0");
-
- ImGui.Separator();
- ImGui.Text("You can override the dungeon settings for each individual dungeon/trial:");
-
- using (var child = ImRaii.Child("DutyConfiguration", new Vector2(-1, 400), true))
- {
- if (child)
- {
- foreach (EExpansionVersion expansion in Enum.GetValues<EExpansionVersion>())
- {
- if (ImGui.CollapsingHeader(expansion.ToString()))
- {
- using var table = ImRaii.Table($"Duties{expansion}", 2, ImGuiTableFlags.SizingFixedFit);
- if (table)
- {
- ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch);
- ImGui.TableSetupColumn("Options", ImGuiTableColumnFlags.WidthFixed, 200f);
-
- if (_contentFinderConditionNames.TryGetValue(expansion, out var cfcNames))
- {
- foreach (var (cfcId, territoryId, name) in cfcNames)
- {
- if (_questRegistry.TryGetDutyByContentFinderConditionId(cfcId,
- out bool autoDutyEnabledByDefault))
- {
- ImGui.TableNextRow();
-
- string[] labels = autoDutyEnabledByDefault
- ? _supportedCfcOptions
- : _unsupportedCfcOptions;
- int value = 0;
- if (_configuration.Duties.WhitelistedDutyCfcIds.Contains(cfcId))
- value = 1;
- if (_configuration.Duties.BlacklistedDutyCfcIds.Contains(cfcId))
- value = 2;
-
- if (ImGui.TableNextColumn())
- {
- ImGui.AlignTextToFramePadding();
- ImGui.TextUnformatted(name);
- if (ImGui.IsItemHovered() && _configuration.Advanced.AdditionalStatusInformation)
- {
- using var tooltip = ImRaii.Tooltip();
- if (tooltip)
- {
- ImGui.TextUnformatted(name);
- ImGui.Separator();
- ImGui.BulletText($"TerritoryId: {territoryId}");
- ImGui.BulletText($"ContentFinderConditionId: {cfcId}");
- }
- }
-
- if (runInstancedContentWithAutoDuty && !_autoDutyIpc.HasPath(cfcId))
- ImGuiComponents.HelpMarker("This duty is not supported by AutoDuty", FontAwesomeIcon.Times, ImGuiColors.DalamudRed);
- }
-
- if (ImGui.TableNextColumn())
- {
- using var _ = ImRaii.PushId($"##Dungeon{cfcId}");
- ImGui.SetNextItemWidth(200);
- if (ImGui.Combo(string.Empty, ref value, labels, labels.Length))
- {
- _configuration.Duties.WhitelistedDutyCfcIds.Remove(cfcId);
- _configuration.Duties.BlacklistedDutyCfcIds.Remove(cfcId);
-
- if (value == 1)
- _configuration.Duties.WhitelistedDutyCfcIds.Add(cfcId);
- else if (value == 2)
- _configuration.Duties.BlacklistedDutyCfcIds.Add(cfcId);
-
- Save();
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
-
- using (ImRaii.Disabled(_configuration.Duties.WhitelistedDutyCfcIds.Count +
- _configuration.Duties.BlacklistedDutyCfcIds.Count == 0))
- {
- if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Copy, "Export to clipboard"))
- {
- var whitelisted =
- _configuration.Duties.WhitelistedDutyCfcIds.Select(x => $"{DutyWhitelistPrefix}{x}");
- var blacklisted =
- _configuration.Duties.BlacklistedDutyCfcIds.Select(x => $"{DutyBlacklistPrefix}{x}");
- string text = DutyClipboardPrefix + Convert.ToBase64String(Encoding.UTF8.GetBytes(
- string.Join(DutyClipboardSeparator, whitelisted.Concat(blacklisted))));
- ImGui.SetClipboardText(text);
- }
- }
-
- ImGui.SameLine();
-
- string? clipboardText = GetClipboardText();
- using (ImRaii.Disabled(clipboardText == null || !clipboardText.StartsWith(DutyClipboardPrefix, StringComparison.InvariantCulture)))
- {
- if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Paste, "Import from Clipboard"))
- {
- clipboardText = clipboardText!.Substring(DutyClipboardPrefix.Length);
- string text = Encoding.UTF8.GetString(Convert.FromBase64String(clipboardText));
-
- _configuration.Duties.WhitelistedDutyCfcIds.Clear();
- _configuration.Duties.BlacklistedDutyCfcIds.Clear();
- foreach (string part in text.Split(DutyClipboardSeparator))
- {
- if (part.StartsWith(DutyWhitelistPrefix, StringComparison.InvariantCulture) &&
- uint.TryParse(part.AsSpan(DutyWhitelistPrefix.Length), CultureInfo.InvariantCulture,
- out uint whitelistedCfcId))
- _configuration.Duties.WhitelistedDutyCfcIds.Add(whitelistedCfcId);
-
- if (part.StartsWith(DutyBlacklistPrefix, StringComparison.InvariantCulture) &&
- uint.TryParse(part.AsSpan(DutyBlacklistPrefix.Length), CultureInfo.InvariantCulture,
- out uint blacklistedCfcId))
- _configuration.Duties.WhitelistedDutyCfcIds.Add(blacklistedCfcId);
- }
- }
- }
-
- ImGui.SameLine();
-
- using (var unused = ImRaii.Disabled(!ImGui.IsKeyDown(ImGuiKey.ModCtrl)))
- {
- if (ImGui.Button("Reset to default"))
- {
- _configuration.Duties.WhitelistedDutyCfcIds.Clear();
- _configuration.Duties.BlacklistedDutyCfcIds.Clear();
- Save();
- }
- }
-
- if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
- ImGui.SetTooltip("Hold CTRL to enable this button.");
- }
- }
-
- private void DrawNotificationsTab()
- {
- using var tab = ImRaii.TabItem("Notifications");
- if (!tab)
- return;
-
- bool enabled = _configuration.Notifications.Enabled;
- if (ImGui.Checkbox("Enable notifications when manual interaction is required", ref enabled))
- {
- _configuration.Notifications.Enabled = enabled;
- Save();
- }
-
- using (ImRaii.Disabled(!_configuration.Notifications.Enabled))
- {
- using (ImRaii.PushIndent())
- {
- var xivChatTypes = Enum.GetValues<XivChatType>()
- .Where(x => x != XivChatType.StandardEmote)
- .ToArray();
- var selectedChatType = Array.IndexOf(xivChatTypes, _configuration.Notifications.ChatType);
- string[] chatTypeNames = xivChatTypes
- .Select(t => t.GetAttribute<XivChatTypeInfoAttribute>()?.FancyName ?? t.ToString())
- .ToArray();
- if (ImGui.Combo("Chat channel", ref selectedChatType, chatTypeNames,
- chatTypeNames.Length))
- {
- _configuration.Notifications.ChatType = xivChatTypes[selectedChatType];
- Save();
- }
-
- ImGui.Separator();
- ImGui.Text("NotificationMaster settings");
- ImGui.SameLine();
- ImGuiComponents.HelpMarker("Requires the plugin 'NotificationMaster' to be installed.");
- using (ImRaii.Disabled(!_notificationMasterIpc.Enabled))
- {
- bool showTrayMessage = _configuration.Notifications.ShowTrayMessage;
- if (ImGui.Checkbox("Show tray notification", ref showTrayMessage))
- {
- _configuration.Notifications.ShowTrayMessage = showTrayMessage;
- Save();
- }
-
- bool flashTaskbar = _configuration.Notifications.FlashTaskbar;
- if (ImGui.Checkbox("Flash taskbar icon", ref flashTaskbar))
- {
- _configuration.Notifications.FlashTaskbar = flashTaskbar;
- Save();
- }
- }
- }
- }
- }
-
- private void DrawAdvancedTab()
- {
- using var tab = ImRaii.TabItem("Advanced");
- if (!tab)
- return;
-
- ImGui.TextColored(ImGuiColors.DalamudRed,
- "Enabling any option here may cause unexpected behavior. Use at your own risk.");
-
- ImGui.Separator();
-
- bool debugOverlay = _configuration.Advanced.DebugOverlay;
- if (ImGui.Checkbox("Enable debug overlay", ref debugOverlay))
- {
- _configuration.Advanced.DebugOverlay = debugOverlay;
- Save();
- }
-
- bool neverFly = _configuration.Advanced.NeverFly;
- if (ImGui.Checkbox("Disable flying (even if unlocked for the zone)", ref neverFly))
- {
- _configuration.Advanced.NeverFly = neverFly;
- Save();
- }
-
- bool additionalStatusInformation = _configuration.Advanced.AdditionalStatusInformation;
- if (ImGui.Checkbox("Draw additional status information", ref additionalStatusInformation))
- {
- _configuration.Advanced.AdditionalStatusInformation = additionalStatusInformation;
- Save();
- }
-
- ImGui.EndTabItem();
- }
-
- private void Save() => _pluginInterface.SavePluginConfig(_configuration);
-
- public void SaveWindowConfig() => Save();
-
- /// <summary>
- /// The default implementation for <see cref="ImGui.GetClipboardText"/> throws an NullReferenceException if the clipboard is empty, maybe also if it doesn't contain text.
- /// </summary>
- private unsafe string? GetClipboardText()
- {
- byte* ptr = ImGuiNative.igGetClipboardText();
- if (ptr == null)
- return null;
-
- int byteCount = 0;
- while (ptr[byteCount] != 0)
- ++byteCount;
- return Encoding.UTF8.GetString(ptr, byteCount);
+ _generalConfigComponent.DrawTab();
+ _dutyConfigComponent.DrawTab();
+ _notificationConfigComponent.DrawTab();
+ _debugConfigComponent.DrawTab();
}
- private sealed record DutyInfo(uint CfcId, uint TerritoryId, string Name);
+ public void SaveWindowConfig() => _pluginInterface.SavePluginConfig(_configuration);
}