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; 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("Preferred Combat Module", ref currentItem, in _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("Preferred Grand Company", ref currentItem2, in _grandCompanyNames, _grandCompanyNames.Length)) { base.Configuration.General.GrandCompany = (FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany)currentItem2; Save(); } int currentItem3 = Array.IndexOf(_classJobIds, base.Configuration.General.CombatJob); if (currentItem3 == -1) { base.Configuration.General.CombatJob = EClassJob.Adventurer; Save(); currentItem3 = 0; } if (ImGui.Combo("Preferred Combat Job", ref currentItem3, in _classJobNames, _classJobNames.Length)) { base.Configuration.General.CombatJob = _classJobIds[currentItem3]; Save(); } 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.ShowIncompleteSeasonalEvents; if (ImGui.Checkbox("Show details for incomplete seasonal events", ref v3)) { base.Configuration.General.ShowIncompleteSeasonalEvents = v3; Save(); } bool v4 = base.Configuration.General.HideSeasonalEventsFromJournalProgress; if (ImGui.Checkbox("Hide Seasonal Events from Journal Progress", ref v4)) { base.Configuration.General.HideSeasonalEventsFromJournalProgress = v4; Save(); } bool v5 = base.Configuration.General.ShowChangelogOnUpdate; if (ImGui.Checkbox("Show changelog window when plugin updates", ref v5)) { base.Configuration.General.ShowChangelogOnUpdate = v5; Save(); } } ImGui.Separator(); ImGui.Text("Questing"); using (ImRaii.PushIndent()) { bool v6 = base.Configuration.General.ConfigureTextAdvance; if (ImGui.Checkbox("Automatically configure TextAdvance with the recommended settings", ref v6)) { base.Configuration.General.ConfigureTextAdvance = v6; Save(); } bool v7 = base.Configuration.General.SkipLowPriorityDuties; if (ImGui.Checkbox("Unlock certain optional dungeons and raids (instead of waiting for completion)", ref v7)) { base.Configuration.General.SkipLowPriorityDuties = v7; 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 text2 = new ImU8String(0, 1); text2.AppendFormatted(contentFinderConditionData.Name); ImGui.BulletText(text2); } } } } ImGui.Spacing(); bool v8 = base.Configuration.General.AutoStepRefreshEnabled; if (ImGui.Checkbox("Automatically refresh quest steps when stuck", ref v8)) { base.Configuration.General.AutoStepRefreshEnabled = v8; 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(!v8)) { ImGui.Indent(); int v9 = base.Configuration.General.AutoStepRefreshDelaySeconds; ImGui.SetNextItemWidth(150f); if (ImGui.SliderInt("Refresh delay (seconds)", ref v9, 10, 180)) { base.Configuration.General.AutoStepRefreshDelaySeconds = v9; Save(); } Vector4 col = new Vector4(0.7f, 0.7f, 0.7f, 1f); ImU8String text2 = new ImU8String(77, 1); text2.AppendLiteral("Quest steps will refresh automatically after "); text2.AppendFormatted(v9); text2.AppendLiteral(" seconds if no progress is made."); ImGui.TextColored(in col, text2); ImGui.Unindent(); } ImGui.Spacing(); ImGui.Separator(); ImGui.Text("Priority Quest Management"); bool v10 = base.Configuration.General.ClearPriorityQuestsOnLogout; if (ImGui.Checkbox("Clear priority quests on character logout", ref v10)) { base.Configuration.General.ClearPriorityQuestsOnLogout = v10; Save(); } bool v11 = base.Configuration.General.ClearPriorityQuestsOnCompletion; if (ImGui.Checkbox("Remove priority quests when completed", ref v11)) { base.Configuration.General.ClearPriorityQuestsOnCompletion = v11; 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; } }