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; }
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;
}
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
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;
internal static class Interact
{
- internal sealed class Factory(Configuration configuration) : ITaskFactory
+ internal sealed class Factory(AutomatonIpc automatonIpc) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
}
else if (step.InteractionType == EInteractionType.Snipe)
{
- if (!configuration.General.AutomaticallyCompleteSnipeTasks)
+ if (!automatonIpc.IsAutoSnipeEnabled)
yield break;
}
else if (step.InteractionType != EInteractionType.Interact)
+++ /dev/null
-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<EnqueueSnipeTaskDelegate> _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<EnqueueSnipeTaskDelegate>(
- "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();
- }
-}
--- /dev/null
+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<AutomatonIpc> _logger;
+ private readonly ICallGateSubscriber<string,bool> _isTweakEnabled;
+ private bool _loggedIpcError;
+
+ public AutomatonIpc(IDalamudPluginInterface pluginInterface, ILogger<AutomatonIpc> logger)
+ {
+ _logger = logger;
+ _isTweakEnabled = pluginInterface.GetIpcSubscriber<string, bool>("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;
+ }
+ }
+ }
+}
serviceCollection.AddSingleton<ChatFunctions>();
serviceCollection.AddSingleton<QuestFunctions>();
serviceCollection.AddSingleton<DalamudReflector>();
- serviceCollection.AddSingleton<AutoSnipeHandler>();
serviceCollection.AddSingleton<AetherCurrentData>();
serviceCollection.AddSingleton<AetheryteData>();
serviceCollection.AddSingleton<QuestionableIpc>();
serviceCollection.AddSingleton<TextAdvanceIpc>();
serviceCollection.AddSingleton<NotificationMasterIpc>();
+ serviceCollection.AddSingleton<AutomatonIpc>();
}
private static void AddTaskFactories(ServiceCollection serviceCollection)
serviceProvider.GetRequiredService<ShopController>();
serviceProvider.GetRequiredService<QuestionableIpc>();
serviceProvider.GetRequiredService<DalamudInitializer>();
- serviceProvider.GetRequiredService<AutoSnipeHandler>().Enable();
serviceProvider.GetRequiredService<TextAdvanceIpc>();
+ serviceProvider.GetRequiredService<AutomatonIpc>();
}
public void Dispose()
_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()
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<PluginInfo> RequiredPlugins =
[
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<PluginInfo> 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<PluginInfo> _recommendedPlugins;
private readonly Configuration _configuration;
private readonly IDalamudPluginInterface _pluginInterface;
private readonly ILogger<OneTimeSetupWindow> _logger;
public OneTimeSetupWindow(Configuration configuration, IDalamudPluginInterface pluginInterface, UiUtils uiUtils,
- DalamudReflector dalamudReflector, ILogger<OneTimeSetupWindow> logger)
+ DalamudReflector dalamudReflector, ILogger<OneTimeSetupWindow> logger, AutomatonIpc automatonIpc)
: base("Questionable Setup###QuestionableOneTimeSetup",
ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoSavedSettings, true)
{
_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;
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);
}
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");
+ }
}
}
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<PluginDetailInfo>? DetailsToCheck = null);
+
+ private sealed record PluginDetailInfo(string DisplayName, Func<bool> Predicate);
}