using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Numerics; using System.Runtime.InteropServices; using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Utility.Raii; using Dalamud.Plugin; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using LLib.GameData; using Lumina.Excel.Sheets; using Questionable.Controller; using Questionable.Data; namespace Questionable.Windows.ConfigComponents; internal sealed class GeneralConfigComponent : ConfigComponent { private static readonly List<(uint Id, string Name)> DefaultMounts; private static readonly List<(EClassJob ClassJob, string Name)> DefaultClassJobs; private readonly QuestRegistry _questRegistry; private readonly TerritoryData _territoryData; private readonly uint[] _mountIds; private readonly string[] _mountNames; private readonly string[] _combatModuleNames = new string[4] { "None", "Boss Mod (VBM)", "Wrath Combo", "Rotation Solver Reborn" }; private readonly string[] _grandCompanyNames = new string[4] { "None (manually pick quest)", "Maelstrom", "Twin Adder", "Immortal Flames" }; private readonly EClassJob[] _classJobIds; private readonly string[] _classJobNames; private string _mountSearchText = string.Empty; private bool _mountComboJustOpened; private string _classJobSearchText = string.Empty; private bool _classJobComboJustOpened; public GeneralConfigComponent(IDalamudPluginInterface pluginInterface, Configuration configuration, IDataManager dataManager, ClassJobUtils classJobUtils, QuestRegistry questRegistry, TerritoryData territoryData) : base(pluginInterface, configuration) { _questRegistry = questRegistry; _territoryData = territoryData; List<(uint, string)> source = (from x in dataManager.GetExcelSheet() where x.RowId != 0 && x.Icon > 0 select (MountId: x.RowId, Name: CapitalizeName(x.Singular.ExtractText())) into x where !string.IsNullOrEmpty(x.Name) select x).OrderBy<(uint, string), string>(((uint MountId, string Name) x) => x.Name, StringComparer.OrdinalIgnoreCase).ToList(); _mountIds = DefaultMounts.Select<(uint, string), uint>(((uint Id, string Name) x) => x.Id).Concat(source.Select<(uint, string), uint>(((uint MountId, string Name) x) => x.MountId)).ToArray(); _mountNames = DefaultMounts.Select<(uint, string), string>(((uint Id, string Name) x) => x.Name).Concat(source.Select<(uint, string), string>(((uint MountId, string Name) x) => x.Name)).ToArray(); List sortedClassJobs = classJobUtils.SortedClassJobs.Select(((EClassJob ClassJob, int Category) x) => x.ClassJob).ToList(); List list = (from x in Enum.GetValues() where x != EClassJob.Adventurer where !x.IsCrafter() && !x.IsGatherer() where !x.IsClass() orderby sortedClassJobs.IndexOf(x) select x).ToList(); _classJobIds = DefaultClassJobs.Select<(EClassJob, string), EClassJob>(((EClassJob ClassJob, string Name) x) => x.ClassJob).Concat(list).ToArray(); _classJobNames = DefaultClassJobs.Select<(EClassJob, string), string>(((EClassJob ClassJob, string Name) x) => x.Name).Concat(list.Select((EClassJob x) => x.ToFriendlyString())).ToArray(); } private static string CapitalizeName(string text) { if (string.IsNullOrEmpty(text)) { return text; } if (string.Equals(text, text.ToLowerInvariant(), StringComparison.Ordinal)) { return CultureInfo.InvariantCulture.TextInfo.ToTitleCase(text); } string[] array = text.Split(' '); for (int i = 0; i < array.Length; i++) { if (array[i].Length > 0 && char.IsLower(array[i][0])) { array[i] = char.ToUpperInvariant(array[i][0]) + array[i].Substring(1); } } return string.Join(" ", array); } public override void DrawTab() { using ImRaii.IEndObject endObject = ImRaii.TabItem("General###General"); if (!endObject) { return; } int currentItem = (int)base.Configuration.General.CombatModule; if (ImGui.Combo((ImU8String)"Preferred Combat Module", ref currentItem, (ReadOnlySpan)_combatModuleNames, _combatModuleNames.Length)) { base.Configuration.General.CombatModule = (Configuration.ECombatModule)currentItem; Save(); } int num = Array.FindIndex(_mountIds, (uint x) => x == base.Configuration.General.MountId); if (num == -1) { num = 0; base.Configuration.General.MountId = _mountIds[num]; Save(); } string text = ((num >= 0 && num < _mountNames.Length) ? _mountNames[num] : "Unknown"); if (ImGui.BeginCombo("Preferred Mount", text, ImGuiComboFlags.HeightLarge)) { if (!_mountComboJustOpened) { ImGui.SetKeyboardFocusHere(); _mountComboJustOpened = true; } ImGui.SetNextItemWidth(-1f); ImGui.InputTextWithHint("##MountSearch", "Search mounts...", ref _mountSearchText, 256); ImGui.Separator(); using (ImRaii.IEndObject endObject2 = ImRaii.Child("##MountScrollArea", new Vector2(0f, 300f), border: false)) { if (endObject2) { string value = _mountSearchText.ToUpperInvariant(); for (int num2 = 0; num2 < _mountNames.Length; num2++) { if (string.IsNullOrEmpty(_mountSearchText) || _mountNames[num2].ToUpperInvariant().Contains(value, StringComparison.Ordinal)) { bool flag = num2 == num; if (ImGui.Selectable(_mountNames[num2], flag)) { base.Configuration.General.MountId = _mountIds[num2]; Save(); _mountSearchText = string.Empty; ImGui.CloseCurrentPopup(); } if (flag) { ImGui.SetItemDefaultFocus(); } } } } } ImGui.EndCombo(); } else { _mountComboJustOpened = false; } int currentItem2 = (int)base.Configuration.General.GrandCompany; if (ImGui.Combo((ImU8String)"Preferred Grand Company", ref currentItem2, (ReadOnlySpan)_grandCompanyNames, _grandCompanyNames.Length)) { base.Configuration.General.GrandCompany = (FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany)currentItem2; Save(); } int num3 = Array.IndexOf(_classJobIds, base.Configuration.General.CombatJob); if (num3 == -1) { base.Configuration.General.CombatJob = EClassJob.Adventurer; Save(); num3 = 0; } string text2 = ((num3 >= 0 && num3 < _classJobNames.Length) ? _classJobNames[num3] : "Unknown"); if (ImGui.BeginCombo("Preferred Combat Job", text2, ImGuiComboFlags.HeightLarge)) { if (!_classJobComboJustOpened) { ImGui.SetKeyboardFocusHere(); _classJobComboJustOpened = true; } ImGui.SetNextItemWidth(-1f); ImGui.InputTextWithHint("##CombatJobSearch", "Search combat jobs...", ref _classJobSearchText, 256); ImGui.Separator(); using (ImRaii.IEndObject endObject3 = ImRaii.Child("##CombatJobScrollArea", new Vector2(0f, 300f), border: false)) { if (endObject3) { string value2 = _classJobSearchText.ToUpperInvariant(); for (int num4 = 0; num4 < _classJobNames.Length; num4++) { if (string.IsNullOrEmpty(_classJobSearchText) || _classJobNames[num4].ToUpperInvariant().Contains(value2, StringComparison.Ordinal)) { bool flag2 = num4 == num3; if (ImGui.Selectable(_classJobNames[num4], flag2)) { base.Configuration.General.CombatJob = _classJobIds[num4]; Save(); _classJobSearchText = string.Empty; ImGui.CloseCurrentPopup(); } if (flag2) { ImGui.SetItemDefaultFocus(); } } } } } ImGui.EndCombo(); } else { _classJobComboJustOpened = false; } ImGui.Separator(); ImGui.Text("UI"); using (ImRaii.PushIndent()) { bool v = base.Configuration.General.HideInAllInstances; if (ImGui.Checkbox("Hide quest window in all instanced duties", ref v)) { base.Configuration.General.HideInAllInstances = v; Save(); } bool v2 = base.Configuration.General.UseEscToCancelQuesting; if (ImGui.Checkbox("Double tap ESC to cancel questing/movement", ref v2)) { base.Configuration.General.UseEscToCancelQuesting = v2; Save(); } bool v3 = base.Configuration.General.StopOnPlayerInput; if (ImGui.Checkbox("Stop automation when manually moving character", ref v3)) { base.Configuration.General.StopOnPlayerInput = v3; Save(); } bool v4 = base.Configuration.General.ShowIncompleteSeasonalEvents; if (ImGui.Checkbox("Show details for incomplete seasonal events", ref v4)) { base.Configuration.General.ShowIncompleteSeasonalEvents = v4; Save(); } bool v5 = base.Configuration.General.HideSeasonalEventsFromJournalProgress; if (ImGui.Checkbox("Hide Seasonal Events from Journal Progress", ref v5)) { base.Configuration.General.HideSeasonalEventsFromJournalProgress = v5; Save(); } bool v6 = base.Configuration.General.ShowChangelogOnUpdate; if (ImGui.Checkbox("Show changelog window when plugin updates", ref v6)) { base.Configuration.General.ShowChangelogOnUpdate = v6; Save(); } } ImGui.Separator(); ImGui.Text("Questing"); using (ImRaii.PushIndent()) { bool v7 = base.Configuration.General.CinemaMode; if (ImGui.Checkbox("Cinema Mode (watch cutscenes)", ref v7)) { base.Configuration.General.CinemaMode = v7; Save(); } ImGui.SameLine(); using (ImRaii.PushFont(UiBuilder.IconFont)) { ImGui.TextDisabled(FontAwesomeIcon.InfoCircle.ToIconString()); } if (ImGui.IsItemHovered()) { using (ImRaii.Tooltip()) { ImGui.Text("When enabled, cutscenes will NOT be automatically skipped."); ImGui.Text("This allows you to experience the story while Questionable"); ImGui.Text("handles navigation, combat, and other gameplay automation."); ImGui.Spacing(); ImGui.TextColored(new Vector4(0.7f, 0.7f, 0.7f, 1f), "Recommended for first-time story playthroughs."); } } bool v8 = base.Configuration.General.ConfigureTextAdvance; if (ImGui.Checkbox("Automatically configure TextAdvance with the recommended settings", ref v8)) { base.Configuration.General.ConfigureTextAdvance = v8; Save(); } bool v9 = base.Configuration.General.SkipLowPriorityDuties; if (ImGui.Checkbox("Unlock certain optional dungeons and raids (instead of waiting for completion)", ref v9)) { base.Configuration.General.SkipLowPriorityDuties = v9; Save(); } ImGui.SameLine(); using (ImRaii.PushFont(UiBuilder.IconFont)) { ImGui.TextDisabled(FontAwesomeIcon.InfoCircle.ToIconString()); } if (ImGui.IsItemHovered()) { using (ImRaii.Tooltip()) { ImGui.Text("Questionable automatically picks up some optional quests (e.g. for aether currents, or the ARR alliance raids)."); ImGui.Text("If this setting is enabled, Questionable will continue with other quests, instead of waiting for manual completion of the duty."); ImGui.Separator(); ImGui.Text("This affects the following dungeons and raids:"); foreach (var lowPriorityContentFinderConditionQuest in _questRegistry.LowPriorityContentFinderConditionQuests) { if (_territoryData.TryGetContentFinderCondition(lowPriorityContentFinderConditionQuest.ContentFinderConditionId, out TerritoryData.ContentFinderConditionData contentFinderConditionData)) { ImU8String text3 = new ImU8String(0, 1); text3.AppendFormatted(contentFinderConditionData.Name); ImGui.BulletText(text3); } } } } ImGui.Spacing(); bool v10 = base.Configuration.General.AutoStepRefreshEnabled; if (ImGui.Checkbox("Automatically refresh quest steps when stuck", ref v10)) { base.Configuration.General.AutoStepRefreshEnabled = v10; Save(); } ImGui.SameLine(); using (ImRaii.PushFont(UiBuilder.IconFont)) { ImGui.TextDisabled(FontAwesomeIcon.InfoCircle.ToIconString()); } if (ImGui.IsItemHovered()) { using (ImRaii.Tooltip()) { ImGui.Text("Questionable will automatically refresh a quest step if it appears to be stuck after the configured delay."); ImGui.Text("This helps resume automated quest completion when interruptions occur."); } } using (ImRaii.Disabled(!v10)) { ImGui.Indent(); int v11 = base.Configuration.General.AutoStepRefreshDelaySeconds; ImGui.SetNextItemWidth(150f); if (ImGui.SliderInt("Refresh delay (seconds)", ref v11, 10, 180)) { base.Configuration.General.AutoStepRefreshDelaySeconds = v11; Save(); } Vector4 col = new Vector4(0.7f, 0.7f, 0.7f, 1f); ImU8String text4 = new ImU8String(77, 1); text4.AppendLiteral("Quest steps will refresh automatically after "); text4.AppendFormatted(v11); text4.AppendLiteral(" seconds if no progress is made."); ImGui.TextColored(in col, text4); ImGui.Unindent(); } ImGui.Spacing(); ImGui.Separator(); ImGui.Text("Priority Quest Management"); bool v12 = base.Configuration.General.ClearPriorityQuestsOnLogout; if (ImGui.Checkbox("Clear priority quests on character logout", ref v12)) { base.Configuration.General.ClearPriorityQuestsOnLogout = v12; Save(); } bool v13 = base.Configuration.General.ClearPriorityQuestsOnCompletion; if (ImGui.Checkbox("Remove priority quests when completed", ref v13)) { base.Configuration.General.ClearPriorityQuestsOnCompletion = v13; Save(); } ImGui.SameLine(); using (ImRaii.PushFont(UiBuilder.IconFont)) { ImGui.TextDisabled(FontAwesomeIcon.InfoCircle.ToIconString()); } if (ImGui.IsItemHovered()) { using (ImRaii.Tooltip()) { ImGui.Text("Automatically removes completed quests from your priority queue."); ImGui.Text("This helps keep your priority list clean without manual intervention."); return; } } } } static GeneralConfigComponent() { int num = 1; List<(uint, string)> list = new List<(uint, string)>(num); CollectionsMarshal.SetCount(list, num); Span<(uint, string)> span = CollectionsMarshal.AsSpan(list); int index = 0; span[index] = (0u, "Mount Roulette"); DefaultMounts = list; index = 1; List<(EClassJob, string)> list2 = new List<(EClassJob, string)>(index); CollectionsMarshal.SetCount(list2, index); Span<(EClassJob, string)> span2 = CollectionsMarshal.AsSpan(list2); num = 0; span2[num] = (EClassJob.Adventurer, "Auto (highest level/item level)"); DefaultClassJobs = list2; } }