using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Numerics; using System.Text; using Dalamud.Bindings.ImGui; 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 Lumina.Excel.Sheets; using Questionable.Controller; using Questionable.Data; using Questionable.External; using Questionable.Model; using Questionable.Model.Questing; namespace Questionable.Windows.ConfigComponents; internal sealed class DutyConfigComponent : ConfigComponent { private sealed record DutyInfo(uint CfcId, uint TerritoryId, string Name); private const string DutyClipboardPrefix = "qst:duty:"; private const string ModePrefix = "M:"; private static readonly string[] DutyModeLabels = new string[3] { "Duty Support", "Unsync (Solo)", "Unsync (Party)" }; private readonly QuestRegistry _questRegistry; private readonly AutoDutyIpc _autoDutyIpc; private readonly Dictionary> _contentFinderConditionNames; public DutyConfigComponent(IDalamudPluginInterface pluginInterface, Configuration configuration, IDataManager dataManager, QuestRegistry questRegistry, AutoDutyIpc autoDutyIpc, TerritoryData territoryData) : base(pluginInterface, configuration) { _questRegistry = questRegistry; _autoDutyIpc = autoDutyIpc; _contentFinderConditionNames = (from x in dataManager.GetExcelSheet() where x.RowId != 0 && !x.Unknown16 orderby x.Unknown15 select x.Content.ValueNullable into x where x.HasValue select x.Value into x select 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, SortKey = x.SortKey } into x group x by x.Expansion).ToDictionary(x => x.Key, x => x.Select(y => new DutyInfo(y.CfcId, y.TerritoryId, ConfigComponent.FormatLevel(y.Level) + " " + y.Name)).ToList()); } public override void DrawTab() { using ImRaii.IEndObject endObject = ImRaii.TabItem("Duties###Duties"); if (!endObject) { return; } bool v = base.Configuration.Duties.RunInstancedContentWithAutoDuty; if (ImGui.Checkbox("Run instanced content with AutoDuty and BossMod", ref v)) { base.Configuration.Duties.RunInstancedContentWithAutoDuty = v; 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."); using (ImRaii.Disabled(!v)) { ImGui.Spacing(); ImGui.Text("Default duty mode:"); ImGui.SameLine(); int currentItem = (int)base.Configuration.Duties.DefaultDutyMode; ImGui.SetNextItemWidth(200f); if (ImGui.Combo("##DefaultDutyMode", ref currentItem, in DutyModeLabels, DutyModeLabels.Length)) { base.Configuration.Duties.DefaultDutyMode = (EDutyMode)currentItem; Save(); } ImGui.SameLine(); ImGuiComponents.HelpMarker("\ufffd Duty Support: Run with NPC party members (level synced)\n\ufffd Unsync (Solo): Run alone at your current level (unsynced)\n\ufffd Unsync (Party): Run with your party at current level (unsynced)\n\nYou can override this setting for individual duties below."); } ImGui.Separator(); using (ImRaii.Disabled(!v)) { 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 settings for each individual dungeon/trial:"); DrawConfigTable(v); DrawEnableAllButton(); ImGui.SameLine(); DrawClipboardButtons(); ImGui.SameLine(); DrawResetButton(); } } private void DrawConfigTable(bool runInstancedContentWithAutoDuty) { using ImRaii.IEndObject endObject = ImRaii.Child("DutyConfiguration", new Vector2(950f, 400f), border: true); if (!endObject) { return; } EExpansionVersion[] values = Enum.GetValues(); for (int i = 0; i < values.Length; i++) { EExpansionVersion eExpansionVersion = values[i]; (int enabledCount, int totalCount) dutyCountsForExpansion = GetDutyCountsForExpansion(eExpansionVersion); int item = dutyCountsForExpansion.enabledCount; int item2 = dutyCountsForExpansion.totalCount; string obj = ((item2 > 0) ? $"{eExpansionVersion.ToFriendlyString()} ({item}/{item2})" : eExpansionVersion.ToFriendlyString()); string key = eExpansionVersion.ToString(); ImGui.SetNextItemOpen(base.Configuration.Duties.ExpansionHeaderStates.GetValueOrDefault(key, defaultValue: false), ImGuiCond.Always); if (ImGui.CollapsingHeader(obj)) { if (!base.Configuration.Duties.ExpansionHeaderStates.GetValueOrDefault(key, defaultValue: false)) { base.Configuration.Duties.ExpansionHeaderStates[key] = true; Save(); } ImU8String table = new ImU8String(6, 1); table.AppendLiteral("Duties"); table.AppendFormatted(eExpansionVersion); using ImRaii.IEndObject endObject2 = ImRaii.Table(table, 3, ImGuiTableFlags.SizingFixedFit); if (!ImRaii.IEndObject.op_True(endObject2)) { continue; } ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch); ImGui.TableSetupColumn("Enabled", ImGuiTableColumnFlags.WidthFixed, 200f); ImGui.TableSetupColumn("Mode", ImGuiTableColumnFlags.WidthFixed, 150f); if (!_contentFinderConditionNames.TryGetValue(eExpansionVersion, out List value)) { continue; } foreach (DutyInfo item3 in value) { item3.Deconstruct(out uint CfcId, out uint TerritoryId, out string Name); uint num = CfcId; uint value2 = TerritoryId; string text = Name; if (!_questRegistry.TryGetDutyByContentFinderConditionId(num, out DutyOptions dutyOptions)) { continue; } ImGui.TableNextRow(); string[] items = (dutyOptions.Enabled ? SupportedCfcOptions : UnsupportedCfcOptions); int currentItem = 0; if (base.Configuration.Duties.WhitelistedDutyCfcIds.Contains(num)) { currentItem = 1; } if (base.Configuration.Duties.BlacklistedDutyCfcIds.Contains(num)) { currentItem = 2; } if (ImGui.TableNextColumn()) { ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted(text); if (ImGui.IsItemHovered() && base.Configuration.Advanced.AdditionalStatusInformation) { using ImRaii.IEndObject endObject3 = ImRaii.Tooltip(); if (endObject3) { ImGui.TextUnformatted(text); ImGui.Separator(); table = new ImU8String(13, 1); table.AppendLiteral("TerritoryId: "); table.AppendFormatted(value2); ImGui.BulletText(table); table = new ImU8String(26, 1); table.AppendLiteral("ContentFinderConditionId: "); table.AppendFormatted(num); ImGui.BulletText(table); } } if (runInstancedContentWithAutoDuty && !_autoDutyIpc.HasPath(num)) { ImGuiComponents.HelpMarker("This duty is not supported by AutoDuty", FontAwesomeIcon.Times, ImGuiColors.DalamudRed); } else if (dutyOptions.Notes.Count > 0) { ConfigComponent.DrawNotes(dutyOptions.Enabled, dutyOptions.Notes); } } if (ImGui.TableNextColumn()) { table = new ImU8String(16, 1); table.AppendLiteral("##DungeonEnabled"); table.AppendFormatted(num); using (ImRaii.PushId(table)) { ImGui.SetNextItemWidth(200f); if (ImGui.Combo(string.Empty, ref currentItem, in items, items.Length)) { base.Configuration.Duties.WhitelistedDutyCfcIds.Remove(num); base.Configuration.Duties.BlacklistedDutyCfcIds.Remove(num); switch (currentItem) { case 1: base.Configuration.Duties.WhitelistedDutyCfcIds.Add(num); break; case 2: base.Configuration.Duties.BlacklistedDutyCfcIds.Add(num); break; } Save(); } } } if (!ImGui.TableNextColumn()) { continue; } table = new ImU8String(13, 1); table.AppendLiteral("##DungeonMode"); table.AppendFormatted(num); using (ImRaii.PushId(table)) { EDutyMode value3; bool flag = base.Configuration.Duties.DutyModeOverrides.TryGetValue(num, out value3); EDutyMode num2 = (flag ? value3 : ((EDutyMode)(-1))); Name = "Use Default"; string[] dutyModeLabels = DutyModeLabels; int num3 = 0; string[] array = new string[1 + dutyModeLabels.Length]; array[num3] = Name; num3++; ReadOnlySpan readOnlySpan = new ReadOnlySpan(dutyModeLabels); readOnlySpan.CopyTo(new Span(array).Slice(num3, readOnlySpan.Length)); num3 += readOnlySpan.Length; string[] items2 = array; int currentItem2 = (int)(num2 + 1); ImGui.SetNextItemWidth(150f); if (ImGui.Combo(string.Empty, ref currentItem2, in items2, items2.Length)) { if (currentItem2 == 0) { base.Configuration.Duties.DutyModeOverrides.Remove(num); } else { base.Configuration.Duties.DutyModeOverrides[num] = (EDutyMode)(currentItem2 - 1); } Save(); } if (ImGui.IsItemHovered()) { EDutyMode eDutyMode = (flag ? value3 : base.Configuration.Duties.DefaultDutyMode); ImGui.SetTooltip(flag ? ("Override: " + DutyModeLabels[(int)eDutyMode]) : ("Using default: " + DutyModeLabels[(int)eDutyMode])); } } } } else if (base.Configuration.Duties.ExpansionHeaderStates.GetValueOrDefault(key, defaultValue: false)) { base.Configuration.Duties.ExpansionHeaderStates[key] = false; Save(); } } } private (int enabledCount, int totalCount) GetDutyCountsForExpansion(EExpansionVersion expansion) { if (!_contentFinderConditionNames.TryGetValue(expansion, out List value)) { return (enabledCount: 0, totalCount: 0); } int num = 0; int num2 = 0; foreach (var (num5, _, _) in value) { if (_questRegistry.TryGetDutyByContentFinderConditionId(num5, out DutyOptions dutyOptions)) { num2++; if (base.Configuration.Duties.WhitelistedDutyCfcIds.Contains(num5) || (!base.Configuration.Duties.BlacklistedDutyCfcIds.Contains(num5) && dutyOptions.Enabled)) { num++; } } } return (enabledCount: num, totalCount: num2); } private void DrawEnableAllButton() { if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.CheckCircle, "Enable All")) { base.Configuration.Duties.BlacklistedDutyCfcIds.Clear(); base.Configuration.Duties.WhitelistedDutyCfcIds.Clear(); foreach (List value in _contentFinderConditionNames.Values) { foreach (var (num3, _, _) in value) { if (_questRegistry.TryGetDutyByContentFinderConditionId(num3, out DutyOptions _)) { base.Configuration.Duties.WhitelistedDutyCfcIds.Add(num3); } } } Save(); } if (ImGui.IsItemHovered()) { ImGui.SetTooltip("Enable all of the duties, use at your own risk."); } } private void DrawClipboardButtons() { using (ImRaii.Disabled(base.Configuration.Duties.WhitelistedDutyCfcIds.Count + base.Configuration.Duties.BlacklistedDutyCfcIds.Count + base.Configuration.Duties.DutyModeOverrides.Count == 0)) { if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Copy, "Export to clipboard")) { IEnumerable first = base.Configuration.Duties.WhitelistedDutyCfcIds.Select((uint x) => $"{"+"}{x}"); IEnumerable second = base.Configuration.Duties.BlacklistedDutyCfcIds.Select((uint x) => $"{"-"}{x}"); IEnumerable second2 = base.Configuration.Duties.DutyModeOverrides.Select((KeyValuePair x) => $"{"M:"}{x.Key}:{x.Value}"); ImGui.SetClipboardText("qst:duty:" + Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Join(";", first.Concat(second).Concat(second2))))); } } ImGui.SameLine(); string text = ImGui.GetClipboardText().Trim(); using (ImRaii.Disabled(string.IsNullOrEmpty(text) || !text.StartsWith("qst:duty:", StringComparison.InvariantCulture))) { if (!ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Paste, "Import from Clipboard")) { return; } text = text.Substring("qst:duty:".Length); string text2 = Encoding.UTF8.GetString(Convert.FromBase64String(text)); base.Configuration.Duties.WhitelistedDutyCfcIds.Clear(); base.Configuration.Duties.BlacklistedDutyCfcIds.Clear(); base.Configuration.Duties.DutyModeOverrides.Clear(); string[] array = text2.Split(";"); foreach (string text3 in array) { if (text3.StartsWith("+", StringComparison.InvariantCulture) && uint.TryParse(text3.AsSpan("+".Length), CultureInfo.InvariantCulture, out var result)) { base.Configuration.Duties.WhitelistedDutyCfcIds.Add(result); } if (text3.StartsWith("-", StringComparison.InvariantCulture) && uint.TryParse(text3.AsSpan("-".Length), CultureInfo.InvariantCulture, out var result2)) { base.Configuration.Duties.BlacklistedDutyCfcIds.Add(result2); } if (!text3.StartsWith("M:", StringComparison.InvariantCulture)) { continue; } ReadOnlySpan span = text3.AsSpan("M:".Length); int num2 = span.IndexOf(':'); if (num2 > 0 && uint.TryParse(span.Slice(0, num2), CultureInfo.InvariantCulture, out var result3)) { int num3 = num2 + 1; if (int.TryParse(span.Slice(num3, span.Length - num3), CultureInfo.InvariantCulture, out var result4) && Enum.IsDefined(typeof(EDutyMode), result4)) { base.Configuration.Duties.DutyModeOverrides[result3] = (EDutyMode)result4; } } } Save(); } } private void DrawResetButton() { using (ImRaii.Disabled(!ImGui.IsKeyDown(ImGuiKey.ModCtrl))) { if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Undo, "Reset to default")) { base.Configuration.Duties.WhitelistedDutyCfcIds.Clear(); base.Configuration.Duties.BlacklistedDutyCfcIds.Clear(); base.Configuration.Duties.DutyModeOverrides.Clear(); base.Configuration.Duties.DefaultDutyMode = EDutyMode.Support; Save(); } } if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) { ImGui.SetTooltip("Hold CTRL to enable this button."); } } }