using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Numerics; using System.Runtime.InteropServices; 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 Questionable.Controller; using Questionable.External; namespace Questionable.Windows.ConfigComponents; internal sealed class PluginConfigComponent : ConfigComponent { private sealed record PluginInfo(string DisplayName, string InternalName, string Details, Uri WebsiteUri, Uri? DalamudRepositoryUri, string? ConfigCommand = null, List? DetailsToCheck = null); private sealed record PluginDetailInfo(string DisplayName, string Details, Func Predicate); private static readonly IReadOnlyList RequiredPlugins = new global::_003C_003Ez__ReadOnlyArray(new PluginInfo[3] { new PluginInfo("vnavmesh", "vnavmesh", "vnavmesh handles the navigation within a zone, moving\nyour character to the next quest-related objective.", new Uri("https://github.com/awgil/ffxiv_navmesh/"), new Uri("https://puni.sh/api/repository/veyn")), new PluginInfo("Lifestream", "Lifestream", "Used to travel to aethernet shards in cities.", new Uri("https://github.com/NightmareXIV/Lifestream"), new Uri("https://github.com/NightmareXIV/MyDalamudPlugins/raw/main/pluginmaster.json")), new PluginInfo("TextAdvance", "TextAdvance", "Automatically accepts and turns in quests, skips cutscenes\nand dialogue.", new Uri("https://github.com/NightmareXIV/TextAdvance"), new Uri("https://github.com/NightmareXIV/MyDalamudPlugins/raw/main/pluginmaster.json")) }); private static readonly ReadOnlyDictionary CombatPlugins = new Dictionary { { Questionable.Configuration.ECombatModule.BossMod, new PluginInfo("Boss Mod (VBM)", "BossMod", string.Empty, new Uri("https://github.com/awgil/ffxiv_bossmod"), new Uri("https://puni.sh/api/repository/veyn")) }, { Questionable.Configuration.ECombatModule.WrathCombo, new PluginInfo("Wrath Combo", "WrathCombo", string.Empty, new Uri("https://github.com/PunishXIV/WrathCombo"), new Uri("https://puni.sh/api/plugins")) }, { Questionable.Configuration.ECombatModule.RotationSolverReborn, new PluginInfo("Rotation Solver Reborn", "RotationSolver", string.Empty, new Uri("https://github.com/FFXIV-CombatReborn/RotationSolverReborn"), new Uri("https://raw.githubusercontent.com/FFXIV-CombatReborn/CombatRebornRepo/main/pluginmaster.json")) } }.AsReadOnly(); private readonly IReadOnlyList _recommendedPlugins; private readonly Configuration _configuration; private readonly CombatController _combatController; private readonly IDalamudPluginInterface _pluginInterface; private readonly UiUtils _uiUtils; private readonly ICommandManager _commandManager; public PluginConfigComponent(IDalamudPluginInterface pluginInterface, Configuration configuration, CombatController combatController, UiUtils uiUtils, ICommandManager commandManager, AutomatonIpc automatonIpc, PandorasBoxIpc pandorasBoxIpc) : base(pluginInterface, configuration) { _configuration = configuration; _combatController = combatController; _pluginInterface = pluginInterface; _uiUtils = uiUtils; _commandManager = commandManager; PluginInfo[] obj = new PluginInfo[5] { new PluginInfo("Artisan", "Artisan", "Handles automatic crafting for quests that require\ncrafted items.", new Uri("https://github.com/PunishXIV/Artisan"), new Uri("https://puni.sh/api/plugins")), null, null, null, null }; Uri websiteUri = new Uri("https://github.com/Jaksuhn/Automaton"); Uri dalamudRepositoryUri = new Uri("https://puni.sh/api/repository/croizat"); int num = 1; List list = new List(num); CollectionsMarshal.SetCount(list, num); Span span = CollectionsMarshal.AsSpan(list); int index = 0; span[index] = new PluginDetailInfo("'Sniper no sniping' enabled", "Automatically completes sniping tasks introduced in Stormblood", () => automatonIpc.IsAutoSnipeEnabled); obj[1] = new PluginInfo("CBT (formerly known as Automaton)", "Automaton", "Automaton is a collection of automation-related tweaks.", websiteUri, dalamudRepositoryUri, "/cbt", list); Uri websiteUri2 = new Uri("https://github.com/PunishXIV/PandorasBox"); Uri dalamudRepositoryUri2 = new Uri("https://puni.sh/api/plugins"); index = 1; List list2 = new List(index); CollectionsMarshal.SetCount(list2, index); span = CollectionsMarshal.AsSpan(list2); num = 0; span[num] = new PluginDetailInfo("'Auto Active Time Maneuver' enabled", "Automatically completes active time maneuvers in\nsingle player instances, trials and raids\"", () => pandorasBoxIpc.IsAutoActiveTimeManeuverEnabled); obj[2] = new PluginInfo("Pandora's Box", "PandorasBox", "Pandora's Box is a collection of tweaks.", websiteUri2, dalamudRepositoryUri2, "/pandora", list2); obj[3] = new PluginInfo("QuestMap", "QuestMap", "Displays quest objectives and markers on the map for\nbetter navigation and tracking.", new Uri("https://github.com/rreminy/QuestMap"), null); obj[4] = new PluginInfo("NotificationMaster", "NotificationMaster", "Sends a configurable out-of-game notification if a quest\nrequires manual actions.", new Uri("https://github.com/NightmareXIV/NotificationMaster"), null); _recommendedPlugins = new global::_003C_003Ez__ReadOnlyArray(obj); } public override void DrawTab() { using ImRaii.IEndObject endObject = ImRaii.TabItem("Dependencies###Plugins"); if (!(!endObject)) { Draw(out var allRequiredInstalled); ImGui.Spacing(); ImGui.Separator(); ImGui.Spacing(); if (allRequiredInstalled) { ImGui.TextColored(ImGuiColors.ParsedGreen, "All required plugins are installed."); } else { ImGui.TextColored(ImGuiColors.DalamudRed, "Required plugins are missing, Questionable will not work properly."); } } } public void Draw(out bool allRequiredInstalled) { float checklistPadding; using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push()) { checklistPadding = ImGui.CalcTextSize(FontAwesomeIcon.Check.ToIconString()).X + ImGui.GetStyle().ItemSpacing.X; } ImGui.Text("Questionable requires the following plugins to work:"); allRequiredInstalled = true; using (ImRaii.PushIndent()) { foreach (PluginInfo requiredPlugin in RequiredPlugins) { allRequiredInstalled &= DrawPlugin(requiredPlugin, checklistPadding); } } ImGui.Spacing(); ImGui.Separator(); ImGui.Spacing(); ImGui.Text("Questionable supports multiple rotation/combat plugins, please pick the one\nyou want to use:"); using (ImRaii.PushIndent()) { using (ImRaii.Disabled(_combatController.IsRunning)) { if (ImGui.RadioButton("No rotation/combat plugin (combat must be done manually)", _configuration.General.CombatModule == Questionable.Configuration.ECombatModule.None)) { _configuration.General.CombatModule = Questionable.Configuration.ECombatModule.None; _pluginInterface.SavePluginConfig(_configuration); } allRequiredInstalled &= DrawCombatPlugin(Questionable.Configuration.ECombatModule.BossMod, checklistPadding); allRequiredInstalled &= DrawCombatPlugin(Questionable.Configuration.ECombatModule.WrathCombo, checklistPadding); allRequiredInstalled &= DrawCombatPlugin(Questionable.Configuration.ECombatModule.RotationSolverReborn, checklistPadding); } } ImGui.Spacing(); ImGui.Separator(); ImGui.Spacing(); ImGui.Text("The following plugins are recommended, but not required:"); using (ImRaii.PushIndent()) { foreach (PluginInfo recommendedPlugin in _recommendedPlugins) { DrawPlugin(recommendedPlugin, checklistPadding); } } } private bool DrawPlugin(PluginInfo plugin, float checklistPadding) { using (ImRaii.PushId("plugin_" + plugin.DisplayName)) { IExposedPlugin exposedPlugin = FindInstalledPlugin(plugin); bool flag = exposedPlugin != null; string text = plugin.DisplayName; if (exposedPlugin != null) { text += $" v{exposedPlugin.Version}"; } _uiUtils.ChecklistItem(text, flag); DrawPluginDetails(plugin, checklistPadding, flag); return flag; } } private bool DrawCombatPlugin(Configuration.ECombatModule combatModule, float checklistPadding) { ImGui.Spacing(); PluginInfo pluginInfo = CombatPlugins[combatModule]; using (ImRaii.PushId("plugin_" + pluginInfo.DisplayName)) { IExposedPlugin exposedPlugin = FindInstalledPlugin(pluginInfo); bool flag = exposedPlugin != null; string text = pluginInfo.DisplayName; if (exposedPlugin != null) { text += $" v{exposedPlugin.Version}"; } if (ImGui.RadioButton(text, _configuration.General.CombatModule == combatModule)) { _configuration.General.CombatModule = combatModule; _pluginInterface.SavePluginConfig(_configuration); } ImGui.SameLine(0f); using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push()) { Vector4 col = (flag ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed); FontAwesomeIcon icon = (flag ? FontAwesomeIcon.Check : FontAwesomeIcon.Times); ImGui.AlignTextToFramePadding(); ImGui.TextColored(in col, icon.ToIconString()); } DrawPluginDetails(pluginInfo, checklistPadding, flag); return flag || _configuration.General.CombatModule != combatModule; } } private void DrawPluginDetails(PluginInfo plugin, float checklistPadding, bool isInstalled) { using (ImRaii.PushIndent(checklistPadding)) { if (!string.IsNullOrEmpty(plugin.Details)) { ImGui.TextUnformatted(plugin.Details); } bool flag = true; if (plugin.DetailsToCheck != null) { foreach (PluginDetailInfo item in plugin.DetailsToCheck) { bool flag2 = item.Predicate(); flag = flag && flag2; _uiUtils.ChecklistItem(item.DisplayName, isInstalled && flag2); if (!string.IsNullOrEmpty(item.Details)) { using (ImRaii.PushIndent(checklistPadding)) { ImGui.TextUnformatted(item.Details); } } } } ImGui.Spacing(); if (isInstalled) { if (!flag && plugin.ConfigCommand != null && plugin.ConfigCommand.StartsWith('/') && ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Cog, "Open configuration")) { _commandManager.ProcessCommand(plugin.ConfigCommand); } return; } if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Globe, "Open Website")) { Util.OpenLink(plugin.WebsiteUri.ToString()); } ImGui.SameLine(); if (plugin.DalamudRepositoryUri != null) { if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Code, "Open Repository")) { Util.OpenLink(plugin.DalamudRepositoryUri.ToString()); } } else { ImGui.AlignTextToFramePadding(); ImGuiComponents.HelpMarker("Available on official Dalamud Repository"); } } } private IExposedPlugin? FindInstalledPlugin(PluginInfo pluginInfo) { return _pluginInterface.InstalledPlugins.FirstOrDefault((IExposedPlugin x) => x.InternalName == pluginInfo.InternalName && x.IsLoaded); } }