using System; using System.Collections.Generic; 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; 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: x.Singular.ToString()) into x where !string.IsNullOrEmpty(x.Name) orderby x.Name select x).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(); } 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 currentItem2 = Array.FindIndex(_mountIds, (uint x) => x == base.Configuration.General.MountId); if (currentItem2 == -1) { currentItem2 = 0; base.Configuration.General.MountId = _mountIds[currentItem2]; Save(); } if (ImGui.Combo("Preferred Mount", ref currentItem2, in _mountNames, _mountNames.Length)) { base.Configuration.General.MountId = _mountIds[currentItem2]; Save(); } int currentItem3 = (int)base.Configuration.General.GrandCompany; if (ImGui.Combo("Preferred Grand Company", ref currentItem3, in _grandCompanyNames, _grandCompanyNames.Length)) { base.Configuration.General.GrandCompany = (FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany)currentItem3; Save(); } int currentItem4 = Array.IndexOf(_classJobIds, base.Configuration.General.CombatJob); if (currentItem4 == -1) { base.Configuration.General.CombatJob = EClassJob.Adventurer; Save(); currentItem4 = 0; } if (ImGui.Combo("Preferred Combat Job", ref currentItem4, in _classJobNames, _classJobNames.Length)) { base.Configuration.General.CombatJob = _classJobIds[currentItem4]; 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("Use 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(); } } ImGui.Separator(); ImGui.Text("Questing"); using (ImRaii.PushIndent()) { bool v5 = base.Configuration.General.ConfigureTextAdvance; if (ImGui.Checkbox("Automatically configure TextAdvance with the recommended settings", ref v5)) { base.Configuration.General.ConfigureTextAdvance = v5; Save(); } bool v6 = base.Configuration.General.SkipLowPriorityDuties; if (ImGui.Checkbox("Unlock certain optional dungeons and raids (instead of waiting for completion)", ref v6)) { base.Configuration.General.SkipLowPriorityDuties = v6; 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 text = new ImU8String(0, 1); text.AppendFormatted(contentFinderConditionData.Name); ImGui.BulletText(text); } } } } ImGui.Spacing(); bool v7 = base.Configuration.General.AutoStepRefreshEnabled; if (ImGui.Checkbox("Automatically refresh quest steps when stuck", ref v7)) { base.Configuration.General.AutoStepRefreshEnabled = v7; 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(!v7)) { ImGui.Indent(); int v8 = base.Configuration.General.AutoStepRefreshDelaySeconds; ImGui.SetNextItemWidth(150f); if (ImGui.SliderInt("Refresh delay (seconds)", ref v8, 10, 180)) { base.Configuration.General.AutoStepRefreshDelaySeconds = v8; Save(); } Vector4 col = new Vector4(0.7f, 0.7f, 0.7f, 1f); ImU8String text = new ImU8String(77, 1); text.AppendLiteral("Quest steps will refresh automatically after "); text.AppendFormatted(v8); text.AppendLiteral(" seconds if no progress is made."); ImGui.TextColored(in col, text); ImGui.Unindent(); } bool v9 = base.Configuration.General.BatchAlliedSocietyTurnIns; if (ImGui.Checkbox("Batch allied society turn-ins", ref v9)) { base.Configuration.General.BatchAlliedSocietyTurnIns = v9; Save(); } ImGui.SameLine(); using (ImRaii.PushFont(UiBuilder.IconFont)) { ImGui.TextDisabled(FontAwesomeIcon.InfoCircle.ToIconString()); } if (ImGui.IsItemHovered()) { using (ImRaii.Tooltip()) { ImGui.TextUnformatted("When enabled, allied society quests that share the same NPC will be turned in in a single conversation (batched)."); ImGui.TextUnformatted("If disabled, each allied society quest will be turned in individually."); 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; } }