diff --git a/Questionable/Configuration.cs b/Questionable/Configuration.cs index 238e4a51..29358b34 100644 --- a/Questionable/Configuration.cs +++ b/Questionable/Configuration.cs @@ -7,7 +7,7 @@ namespace Questionable; internal sealed class Configuration : IPluginConfiguration { - public const int PluginSetupVersion = 1; + public const int PluginSetupVersion = 2; public int Version { get; set; } =1 ; public int PluginSetupCompleteVersion { get; set; } @@ -28,7 +28,6 @@ internal sealed class Configuration : IPluginConfiguration public bool HideInAllInstances { get; set; } = true; public bool UseEscToCancelQuesting { get; set; } = true; public bool ShowIncompleteSeasonalEvents { get; set; } = true; - public bool AutomaticallyCompleteSnipeTasks { get; set; } public bool ConfigureTextAdvance { get; set; } = true; } diff --git a/Questionable/Controller/Steps/Common/SendNotification.cs b/Questionable/Controller/Steps/Common/SendNotification.cs index 5edb743c..7334ca07 100644 --- a/Questionable/Controller/Steps/Common/SendNotification.cs +++ b/Questionable/Controller/Steps/Common/SendNotification.cs @@ -12,14 +12,14 @@ namespace Questionable.Controller.Steps.Common; internal static class SendNotification { internal sealed class Factory( - Configuration configuration, + AutomatonIpc automatonIpc, TerritoryData territoryData) : SimpleTaskFactory { public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step) { return step.InteractionType switch { - EInteractionType.Snipe when !configuration.General.AutomaticallyCompleteSnipeTasks => + EInteractionType.Snipe when !automatonIpc.IsAutoSnipeEnabled => new Task(step.InteractionType, step.Comment), EInteractionType.Duty => new Task(step.InteractionType, step.ContentFinderConditionId.HasValue diff --git a/Questionable/Controller/Steps/Interactions/Interact.cs b/Questionable/Controller/Steps/Interactions/Interact.cs index ac8f5109..0ca70c11 100644 --- a/Questionable/Controller/Steps/Interactions/Interact.cs +++ b/Questionable/Controller/Steps/Interactions/Interact.cs @@ -8,6 +8,7 @@ using FFXIVClientStructs.FFXIV.Client.Game; using Microsoft.Extensions.Logging; using Questionable.Controller.Steps.Shared; using Questionable.Controller.Utils; +using Questionable.External; using Questionable.Functions; using Questionable.Model; using Questionable.Model.Questing; @@ -16,7 +17,7 @@ namespace Questionable.Controller.Steps.Interactions; internal static class Interact { - internal sealed class Factory(Configuration configuration) : ITaskFactory + internal sealed class Factory(AutomatonIpc automatonIpc) : ITaskFactory { public IEnumerable CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { @@ -43,7 +44,7 @@ internal static class Interact } else if (step.InteractionType == EInteractionType.Snipe) { - if (!configuration.General.AutomaticallyCompleteSnipeTasks) + if (!automatonIpc.IsAutoSnipeEnabled) yield break; } else if (step.InteractionType != EInteractionType.Interact) diff --git a/Questionable/Controller/Utils/AutoSnipeHandler.cs b/Questionable/Controller/Utils/AutoSnipeHandler.cs deleted file mode 100644 index dc48c3ac..00000000 --- a/Questionable/Controller/Utils/AutoSnipeHandler.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using Dalamud.Hooking; -using Dalamud.Plugin.Services; -using FFXIVClientStructs.FFXIV.Client.Game.Event; -using FFXIVClientStructs.FFXIV.Common.Lua; - -namespace Questionable.Controller.Utils; - -internal sealed unsafe class AutoSnipeHandler : IDisposable -{ - private readonly QuestController _questController; - private readonly Configuration _configuration; - private readonly Hook _enqueueSnipeTaskHook; - - private delegate ulong EnqueueSnipeTaskDelegate(EventSceneModuleImplBase* scene, lua_State* state); - - public AutoSnipeHandler(QuestController questController, Configuration configuration, IGameInteropProvider gameInteropProvider) - { - _questController = questController; - _configuration = configuration; - - _enqueueSnipeTaskHook = - gameInteropProvider.HookFromSignature( - "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 50 48 8B F1 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8B 4C 24 ??", - EnqueueSnipeTask); - } - - public void Enable() => _enqueueSnipeTaskHook.Enable(); - - private ulong EnqueueSnipeTask(EventSceneModuleImplBase* scene, lua_State* state) - { - if (_configuration.General.AutomaticallyCompleteSnipeTasks && _questController.IsRunning) - { - var val = state->top; - val->tt = 3; - val->value.n = 1; - state->top += 1; - return 1; - } - else - return _enqueueSnipeTaskHook.Original.Invoke(scene, state); - } - - public void Dispose() - { - _enqueueSnipeTaskHook.Dispose(); - } -} diff --git a/Questionable/External/AutomatonIpc.cs b/Questionable/External/AutomatonIpc.cs new file mode 100644 index 00000000..69e94678 --- /dev/null +++ b/Questionable/External/AutomatonIpc.cs @@ -0,0 +1,40 @@ +using Dalamud.Plugin; +using Dalamud.Plugin.Ipc; +using Dalamud.Plugin.Ipc.Exceptions; +using Microsoft.Extensions.Logging; + +namespace Questionable.External; + +internal sealed class AutomatonIpc +{ + private readonly ILogger _logger; + private readonly ICallGateSubscriber _isTweakEnabled; + private bool _loggedIpcError; + + public AutomatonIpc(IDalamudPluginInterface pluginInterface, ILogger logger) + { + _logger = logger; + _isTweakEnabled = pluginInterface.GetIpcSubscriber("Automaton.IsTweakEnabled"); + logger.LogWarning("Automaton x {IsTweakEnabled}", IsAutoSnipeEnabled); + } + + public bool IsAutoSnipeEnabled + { + get + { + try + { + return _isTweakEnabled.InvokeFunc("AutoSnipeQuests"); + } + catch (IpcError e) + { + if (!_loggedIpcError) + { + _loggedIpcError = true; + _logger.LogWarning(e, "Could not query automaton for tweak status, probably not installed"); + } + return false; + } + } + } +} diff --git a/Questionable/QuestionablePlugin.cs b/Questionable/QuestionablePlugin.cs index 7e9fb3f5..8cc59d16 100644 --- a/Questionable/QuestionablePlugin.cs +++ b/Questionable/QuestionablePlugin.cs @@ -112,7 +112,6 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); @@ -128,6 +127,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); } private static void AddTaskFactories(ServiceCollection serviceCollection) @@ -300,8 +300,8 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceProvider.GetRequiredService(); serviceProvider.GetRequiredService(); serviceProvider.GetRequiredService(); - serviceProvider.GetRequiredService().Enable(); serviceProvider.GetRequiredService(); + serviceProvider.GetRequiredService(); } public void Dispose() diff --git a/Questionable/Windows/ConfigWindow.cs b/Questionable/Windows/ConfigWindow.cs index 97847de5..27c968a0 100644 --- a/Questionable/Windows/ConfigWindow.cs +++ b/Questionable/Windows/ConfigWindow.cs @@ -115,18 +115,6 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig _configuration.General.ConfigureTextAdvance = configureTextAdvance; Save(); } - - if (ImGui.CollapsingHeader("Cheats")) - { - ImGui.TextColored(ImGuiColors.DalamudRed, - "This setting will be removed in a future version, and will be\navailable through TextAdvance instead."); - bool automaticallyCompleteSnipeTasks = _configuration.General.AutomaticallyCompleteSnipeTasks; - if (ImGui.Checkbox("Automatically complete snipe tasks", ref automaticallyCompleteSnipeTasks)) - { - _configuration.General.AutomaticallyCompleteSnipeTasks = automaticallyCompleteSnipeTasks; - Save(); - } - } } private void DrawNotificationsTab() diff --git a/Questionable/Windows/OneTimeSetupWindow.cs b/Questionable/Windows/OneTimeSetupWindow.cs index 2ec22702..f1c71552 100644 --- a/Questionable/Windows/OneTimeSetupWindow.cs +++ b/Questionable/Windows/OneTimeSetupWindow.cs @@ -10,10 +10,11 @@ using ImGuiNET; using LLib; using LLib.ImGui; using Microsoft.Extensions.Logging; +using Questionable.External; namespace Questionable.Windows; -internal sealed class OneTimeSetupWindow : LWindow, IDisposable +internal sealed class OneTimeSetupWindow : LWindow { private static readonly IReadOnlyList RequiredPlugins = [ @@ -22,35 +23,24 @@ internal sealed class OneTimeSetupWindow : LWindow, IDisposable vnavmesh handles the navigation within a zone, moving your character to the next quest-related objective. """, - new Uri("https://github.com/awgil/ffxiv_navmesh/")), + new Uri("https://github.com/awgil/ffxiv_navmesh/"), + new Uri("https://puni.sh/api/repository/veyn")), new("Lifestream", """ Used to travel to aethernet shards in cities. """, - new Uri("https://github.com/NightmareXIV/Lifestream")), + new Uri("https://github.com/NightmareXIV/Lifestream"), + new Uri("https://github.com/NightmareXIV/MyDalamudPlugins/raw/main/pluginmaster.json")), new("TextAdvance", """ Automatically accepts and turns in quests, skips cutscenes and dialogue. """, - new Uri("https://github.com/NightmareXIV/TextAdvance")), + new Uri("https://github.com/NightmareXIV/TextAdvance"), + new Uri("https://github.com/NightmareXIV/MyDalamudPlugins/raw/main/pluginmaster.json")), ]; - private static readonly IReadOnlyList RecommendedPlugins = - [ - new("Rotation Solver Reborn", - """ - Automatically handles most combat interactions you encounter - during quests, including being interrupted by mobs. - """, - new Uri("https://github.com/FFXIV-CombatReborn/RotationSolverReborn")), - new("NotificationMaster", - """ - Sends a configurable out-of-game notification if a quest - requires manual actions. - """, - new Uri("https://github.com/NightmareXIV/NotificationMaster")), - ]; + private readonly IReadOnlyList _recommendedPlugins; private readonly Configuration _configuration; private readonly IDalamudPluginInterface _pluginInterface; @@ -59,7 +49,7 @@ internal sealed class OneTimeSetupWindow : LWindow, IDisposable private readonly ILogger _logger; public OneTimeSetupWindow(Configuration configuration, IDalamudPluginInterface pluginInterface, UiUtils uiUtils, - DalamudReflector dalamudReflector, ILogger logger) + DalamudReflector dalamudReflector, ILogger logger, AutomatonIpc automatonIpc) : base("Questionable Setup###QuestionableOneTimeSetup", ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoSavedSettings, true) { @@ -69,6 +59,33 @@ internal sealed class OneTimeSetupWindow : LWindow, IDisposable _dalamudReflector = dalamudReflector; _logger = logger; + _recommendedPlugins = + [ + new("Rotation Solver Reborn", + """ + Automatically handles most combat interactions you encounter + during quests, including being interrupted by mobs. + """, + new Uri("https://github.com/FFXIV-CombatReborn/RotationSolverReborn"), + new Uri( + "https://raw.githubusercontent.com/FFXIV-CombatReborn/CombatRebornRepo/main/pluginmaster.json")), + new PluginInfo("Automaton", + """ + Automaton is a collection of automation-related tweaks. + The 'Sniper no sniping' tweak can complete snipe tasks automatically. + """, + new Uri("https://github.com/Jaksuhn/Automaton"), + new Uri("https://puni.sh/api/repository/croizat"), + [new PluginDetailInfo("'Sniper no sniping' enabled", () => automatonIpc.IsAutoSnipeEnabled)]), + new("NotificationMaster", + """ + Sends a configurable out-of-game notification if a quest + requires manual actions. + """, + new Uri("https://github.com/NightmareXIV/NotificationMaster"), + null), + ]; + RespectCloseHotkey = false; ShowCloseButton = false; AllowPinning = false; @@ -101,7 +118,7 @@ internal sealed class OneTimeSetupWindow : LWindow, IDisposable ImGui.Text("The following plugins are recommended, but not required:"); using (ImRaii.PushIndent()) { - foreach (var plugin in RecommendedPlugins) + foreach (var plugin in _recommendedPlugins) DrawPlugin(plugin, checklistPadding); } @@ -149,8 +166,28 @@ internal sealed class OneTimeSetupWindow : LWindow, IDisposable using (ImRaii.PushIndent(checklistPadding)) { ImGui.TextUnformatted(plugin.Details); - if (!isInstalled && ImGui.Button("Open Repository")) - Util.OpenLink(plugin.Uri.ToString()); + if (plugin.DetailsToCheck != null) + { + foreach (var detail in plugin.DetailsToCheck) + _uiUtils.ChecklistItem(detail.DisplayName, isInstalled && detail.Predicate()); + } + + ImGui.Spacing(); + + 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"); + } } } @@ -162,12 +199,12 @@ internal sealed class OneTimeSetupWindow : LWindow, IDisposable return _dalamudReflector.TryGetDalamudPlugin(internalName, out _, suppressErrors: true, ignoreCache: true); } - public void Dispose() - { - } - private sealed record PluginInfo( string DisplayName, string Details, - Uri Uri); + Uri WebsiteUri, + Uri? DalamudRepositoryUri, + List? DetailsToCheck = null); + + private sealed record PluginDetailInfo(string DisplayName, Func Predicate); }