Add notification settings for when manual interactions are required v3.13
authorLiza Carvelli <liza@carvel.li>
Sun, 3 Nov 2024 21:25:03 +0000 (22:25 +0100)
committerLiza Carvelli <liza@carvel.li>
Sun, 3 Nov 2024 21:25:03 +0000 (22:25 +0100)
14 files changed:
.gitmodules
Directory.Build.targets
Questionable.sln
Questionable/Configuration.cs
Questionable/Controller/Steps/Common/SendNotification.cs [new file with mode: 0644]
Questionable/Controller/Steps/Shared/WaitAtEnd.cs
Questionable/Data/TerritoryData.cs
Questionable/External/NotificationMasterIpc.cs [new file with mode: 0644]
Questionable/Questionable.csproj
Questionable/QuestionablePlugin.cs
Questionable/Windows/ConfigWindow.cs
Questionable/Windows/OneTimeSetupWindow.cs
Questionable/packages.lock.json
vendor/NotificationMasterAPI [new submodule]

index 0bc08a361743a42d7aa7a0d391142562aa3e1405..832da345f4f02f99190e6b645180124ace76844a 100644 (file)
@@ -4,3 +4,6 @@
 [submodule "vendor/ECommons"]
        path = vendor/ECommons
        url = https://github.com/NightmareXIV/ECommons.git
+[submodule "vendor/NotificationMasterAPI"]
+       path = vendor/NotificationMasterAPI
+       url = https://github.com/NightmareXIV/NotificationMasterAPI.git
index 9b59f0ef5f1f917320ac06cf28fa4bdf3a917806..1d11428021567a8ed01b925380b963ef50712780 100644 (file)
@@ -1,5 +1,5 @@
 <Project>
     <PropertyGroup>
-        <Version>3.12</Version>
+        <Version>3.13</Version>
     </PropertyGroup>
 </Project>
index ad259b03ed921708add111863c1d484c067d1b27..df8987891d21a3c491a360b411b4da1fd8957ffa 100644 (file)
@@ -26,6 +26,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
                Directory.Build.targets = Directory.Build.targets
        EndProjectSection
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "vendor", "vendor", "{8F5EC9D5-4CE7-433B-BB3A-782500E84DDB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NotificationMasterAPI", "vendor\NotificationMasterAPI\NotificationMasterAPI\NotificationMasterAPI.csproj", "{9BD494ED-22F2-487B-BCE1-435399A8720E}"
+EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Debug|x64 = Debug|x64
@@ -68,8 +72,16 @@ Global
                {A12D7B4B-8E6E-4DCF-A41A-12F62E9FF94B}.Debug|x64.Build.0 = Debug|x64
                {A12D7B4B-8E6E-4DCF-A41A-12F62E9FF94B}.Release|x64.ActiveCfg = Release|x64
                {A12D7B4B-8E6E-4DCF-A41A-12F62E9FF94B}.Release|x64.Build.0 = Release|x64
+               {9BD494ED-22F2-487B-BCE1-435399A8720E}.Debug|x64.ActiveCfg = Debug|x64
+               {9BD494ED-22F2-487B-BCE1-435399A8720E}.Debug|x64.Build.0 = Debug|x64
+               {9BD494ED-22F2-487B-BCE1-435399A8720E}.Release|x64.ActiveCfg = Release|x64
+               {9BD494ED-22F2-487B-BCE1-435399A8720E}.Release|x64.Build.0 = Release|x64
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
        EndGlobalSection
+       GlobalSection(NestedProjects) = preSolution
+               {A12D7B4B-8E6E-4DCF-A41A-12F62E9FF94B} = {8F5EC9D5-4CE7-433B-BB3A-782500E84DDB}
+               {9BD494ED-22F2-487B-BCE1-435399A8720E} = {8F5EC9D5-4CE7-433B-BB3A-782500E84DDB}
+       EndGlobalSection
 EndGlobal
index fb96ce3533ad76f1fff6e705acea98a90236e98b..238e4a511adf1496722f97df121e2240dd265d26 100644 (file)
@@ -1,4 +1,5 @@
 using Dalamud.Configuration;
+using Dalamud.Game.Text;
 using FFXIVClientStructs.FFXIV.Client.UI.Agent;
 using LLib.ImGui;
 
@@ -8,9 +9,10 @@ internal sealed class Configuration : IPluginConfiguration
 {
     public const int PluginSetupVersion = 1;
 
-    public int Version { get; set; } = 1;
+    public int Version { get; set; } =;
     public int PluginSetupCompleteVersion { get; set; }
     public GeneralConfiguration General { get; } = new();
+    public NotificationConfiguration Notifications { get; } = new();
     public AdvancedConfiguration Advanced { get; } = new();
     public WindowConfig DebugWindowConfig { get; } = new();
     public WindowConfig ConfigWindowConfig { get; } = new();
@@ -30,6 +32,14 @@ internal sealed class Configuration : IPluginConfiguration
         public bool ConfigureTextAdvance { get; set; } = true;
     }
 
+    internal sealed class NotificationConfiguration
+    {
+        public bool Enabled { get; set; } = true;
+        public XivChatType ChatType { get; set; } = XivChatType.Debug;
+        public bool ShowTrayMessage { get; set; }
+        public bool FlashTaskbar { get; set; }
+    }
+
     internal sealed class AdvancedConfiguration
     {
         public bool DebugOverlay { get; set; }
diff --git a/Questionable/Controller/Steps/Common/SendNotification.cs b/Questionable/Controller/Steps/Common/SendNotification.cs
new file mode 100644 (file)
index 0000000..76b8a81
--- /dev/null
@@ -0,0 +1,84 @@
+using Dalamud.Game.Text;
+using Dalamud.Game.Text.SeStringHandling;
+using Dalamud.Plugin.Services;
+using Questionable.External;
+using Questionable.Model.Questing;
+
+namespace Questionable.Controller.Steps.Common;
+
+internal static class SendNotification
+{
+    internal sealed record Task(EInteractionType InteractionType, string? Comment) : ITask
+    {
+        public override string ToString() => "SendNotification";
+    }
+
+    internal sealed class Executor(
+        NotificationMasterIpc notificationMasterIpc,
+        IChatGui chatGui,
+        Configuration configuration) : TaskExecutor<Task>
+    {
+        protected override bool Start()
+        {
+            if (!configuration.Notifications.Enabled)
+                return false;
+
+            string text = Task.InteractionType switch
+            {
+                EInteractionType.Duty => "Duty",
+                EInteractionType.SinglePlayerDuty => "Single player duty",
+                EInteractionType.Instruction or EInteractionType.WaitForManualProgress or EInteractionType.Snipe =>
+                    "Manual interaction required",
+                _ => $"{Task.InteractionType}",
+            };
+
+            if (!string.IsNullOrEmpty(Task.Comment))
+                text += $" - {Task.Comment}";
+
+            if (configuration.Notifications.ChatType != XivChatType.None)
+            {
+                var message = configuration.Notifications.ChatType switch
+                {
+                    XivChatType.Say
+                        or XivChatType.Shout
+                        or XivChatType.TellOutgoing
+                        or XivChatType.TellIncoming
+                        or XivChatType.Party
+                        or XivChatType.Alliance
+                        or (>= XivChatType.Ls1 and <= XivChatType.Ls8)
+                        or XivChatType.FreeCompany
+                        or XivChatType.NoviceNetwork
+                        or XivChatType.Yell
+                        or XivChatType.CrossParty
+                        or XivChatType.PvPTeam
+                        or XivChatType.CrossLinkShell1
+                        or XivChatType.NPCDialogue
+                        or XivChatType.NPCDialogueAnnouncements
+                        or (>= XivChatType.CrossLinkShell2 and <= XivChatType.CrossLinkShell8)
+                        => new XivChatEntry
+                        {
+                            Message = text,
+                            Type = configuration.Notifications.ChatType,
+                            Name = new SeStringBuilder()
+                                .AddUiForeground(CommandHandler.MessageTag, CommandHandler.TagColor)
+                                .Build(),
+                        },
+                    _ => new XivChatEntry
+                    {
+                        Message = new SeStringBuilder()
+                            .AddUiForeground($"[{CommandHandler.MessageTag}] ", CommandHandler.TagColor)
+                            .Append(text)
+                            .Build(),
+                        Type = configuration.Notifications.ChatType,
+                    }
+                };
+                chatGui.Print(message);
+            }
+
+            notificationMasterIpc.Notify(text);
+            return true;
+        }
+
+        public override ETaskResult Update() => ETaskResult.TaskComplete;
+    }
+}
index d64c009bf503ddf3eb3a7d1fd3b528049ffa82c8..84d2e1ca95183cf4171875d4c17c8f786eae9749 100644 (file)
@@ -6,6 +6,7 @@ using System.Numerics;
 using Dalamud.Game.ClientState.Conditions;
 using Dalamud.Plugin.Services;
 using Questionable.Controller.Steps.Common;
+using Questionable.Controller.Steps.Interactions;
 using Questionable.Controller.Utils;
 using Questionable.Data;
 using Questionable.Functions;
@@ -19,7 +20,8 @@ internal static class WaitAtEnd
     internal sealed class Factory(
         IClientState clientState,
         ICondition condition,
-        TerritoryData territoryData)
+        TerritoryData territoryData,
+        Configuration configuration)
         : ITaskFactory
     {
         public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
@@ -47,12 +49,28 @@ internal static class WaitAtEnd
 
                 case EInteractionType.WaitForManualProgress:
                 case EInteractionType.Instruction:
-                case EInteractionType.Snipe:
                     return [new WaitNextStepOrSequence()];
 
+                case EInteractionType.Snipe:
+                    if (configuration.General.AutomaticallyCompleteSnipeTasks)
+                        return [new WaitNextStepOrSequence()];
+                    else
+                        return [
+                            new SendNotification.Task(step.InteractionType, step.Comment),
+                            new WaitNextStepOrSequence()
+                        ];
+
                 case EInteractionType.Duty:
+                    return [
+                        new SendNotification.Task(step.InteractionType, step.ContentFinderConditionId.HasValue ? territoryData.GetContentFinderConditionName(step.ContentFinderConditionId.Value) : step.Comment),
+                        new EndAutomation(),
+                    ];
+
                 case EInteractionType.SinglePlayerDuty:
-                    return [new EndAutomation()];
+                    return [
+                        new SendNotification.Task(step.InteractionType, quest.Info.Name),
+                        new EndAutomation()
+                    ];
 
                 case EInteractionType.WalkTo:
                 case EInteractionType.Jump:
index 0b20d9a9ecca426f95821a15c8618ee84db5afc4..e2d471c34c8248466025b66112def5a6e1221d2d 100644 (file)
@@ -14,6 +14,7 @@ internal sealed class TerritoryData
     private readonly ImmutableHashSet<ushort> _territoriesWithMount;
     private readonly ImmutableDictionary<ushort, uint> _dutyTerritories;
     private readonly ImmutableDictionary<ushort, string> _instanceNames;
+    private readonly ImmutableDictionary<uint, string> _contentFinderConditionNames;
 
     public TerritoryData(IDataManager dataManager)
     {
@@ -40,6 +41,10 @@ internal sealed class TerritoryData
         _instanceNames = dataManager.GetExcelSheet<ContentFinderCondition>()!
             .Where(x => x.RowId > 0 && x.Content != 0 && x.ContentLinkType == 1 && x.ContentType.Row != 6)
             .ToImmutableDictionary(x => x.Content, x => x.Name.ToString());
+
+        _contentFinderConditionNames = dataManager.GetExcelSheet<ContentFinderCondition>()!
+            .Where(x => x.RowId > 0 && x.Content != 0 && x.ContentLinkType == 1 && x.ContentType.Row != 6)
+            .ToImmutableDictionary(x => x.RowId, x => x.Name.ToString());
     }
 
     public string? GetName(ushort territoryId) => _territoryNames.GetValueOrDefault(territoryId);
@@ -61,4 +66,6 @@ internal sealed class TerritoryData
         _dutyTerritories.TryGetValue(territoryId, out uint contentType) && contentType == 7;
 
     public string? GetInstanceName(ushort instanceId) => _instanceNames.GetValueOrDefault(instanceId);
+
+    public string? GetContentFinderConditionName(uint cfcId) => _contentFinderConditionNames.GetValueOrDefault(cfcId);
 }
diff --git a/Questionable/External/NotificationMasterIpc.cs b/Questionable/External/NotificationMasterIpc.cs
new file mode 100644 (file)
index 0000000..7a528c6
--- /dev/null
@@ -0,0 +1,24 @@
+using Dalamud.Plugin;
+using NotificationMasterAPI;
+
+namespace Questionable.External;
+
+internal sealed class NotificationMasterIpc(IDalamudPluginInterface pluginInterface, Configuration configuration)
+{
+    private readonly NotificationMasterApi _api = new(pluginInterface);
+
+    public bool Enabled => _api.IsIPCReady();
+
+    public void Notify(string message)
+    {
+        var config = configuration.Notifications;
+        if (!config.Enabled)
+            return;
+
+        if (config.ShowTrayMessage)
+            _api.DisplayTrayNotification("Questionable", message);
+
+        if (config.FlashTaskbar)
+            _api.FlashTaskbarIcon();
+    }
+}
index 8d1a3c894c72bd796a1b949d0374f8b4fe6133ca..64df5619d2c04799f0c3b2509346744a7d4abb4f 100644 (file)
@@ -21,5 +21,6 @@
         <ProjectReference Include="..\LLib\LLib.csproj"/>
         <ProjectReference Include="..\Questionable.Model\Questionable.Model.csproj"/>
         <ProjectReference Include="..\QuestPaths\QuestPaths.csproj"/>
+        <ProjectReference Include="..\vendor\NotificationMasterAPI\NotificationMasterAPI\NotificationMasterAPI.csproj" />
     </ItemGroup>
 </Project>
index 5e8dc11d8b5d75c24b41fa32327d055f8f04383c..b97158c61a9dc2e5f8424da32d5d996a16934645 100644 (file)
@@ -127,6 +127,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
         serviceCollection.AddSingleton<ArtisanIpc>();
         serviceCollection.AddSingleton<QuestionableIpc>();
         serviceCollection.AddSingleton<TextAdvanceIpc>();
+        serviceCollection.AddSingleton<NotificationMasterIpc>();
     }
 
     private static void AddTaskFactories(ServiceCollection serviceCollection)
@@ -205,6 +206,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
         serviceCollection.AddTaskExecutor<InitiateLeve.Initiate, InitiateLeve.InitiateExecutor>();
         serviceCollection.AddTaskExecutor<InitiateLeve.SelectDifficulty, InitiateLeve.SelectDifficultyExecutor>();
 
+        serviceCollection.AddTaskExecutor<SendNotification.Task, SendNotification.Executor>();
         serviceCollection.AddTaskExecutor<WaitCondition.Task, WaitCondition.WaitConditionExecutor>();
         serviceCollection.AddTaskFactory<WaitAtEnd.Factory>();
         serviceCollection.AddTaskExecutor<WaitAtEnd.WaitDelay, WaitAtEnd.WaitDelayExecutor>();
index 0416943f73b74501515e57c3346b205d9f080197..7bcd76817928817a460859e10933e24f18cb97e8 100644 (file)
@@ -1,12 +1,17 @@
 using System;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
+using Dalamud.Game.Text;
 using Dalamud.Interface.Colors;
+using Dalamud.Interface.Components;
+using Dalamud.Interface.Utility.Raii;
 using Dalamud.Plugin;
 using Dalamud.Plugin.Services;
+using Dalamud.Utility;
 using ImGuiNET;
 using LLib.ImGui;
 using Lumina.Excel.GeneratedSheets;
+using Questionable.External;
 using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany;
 
 namespace Questionable.Windows;
@@ -14,6 +19,7 @@ namespace Questionable.Windows;
 internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
 {
     private readonly IDalamudPluginInterface _pluginInterface;
+    private readonly NotificationMasterIpc _notificationMasterIpc;
     private readonly Configuration _configuration;
 
     private readonly uint[] _mountIds;
@@ -23,10 +29,11 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
         ["None (manually pick quest)", "Maelstrom", "Twin Adder", "Immortal Flames"];
 
     [SuppressMessage("Performance", "CA1861", Justification = "One time initialization")]
-    public ConfigWindow(IDalamudPluginInterface pluginInterface, Configuration configuration, IDataManager dataManager)
+    public ConfigWindow(IDalamudPluginInterface pluginInterface, NotificationMasterIpc notificationMasterIpc, Configuration configuration, IDataManager dataManager)
         : base("Config - Questionable###QuestionableConfig", ImGuiWindowFlags.AlwaysAutoResize)
     {
         _pluginInterface = pluginInterface;
+        _notificationMasterIpc = notificationMasterIpc;
         _configuration = configuration;
 
         var mounts = dataManager.GetExcelSheet<Mount>()!
@@ -43,107 +50,172 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
 
     public override void Draw()
     {
-        if (ImGui.BeginTabBar("QuestionableConfigTabs"))
+        using var tabBar = ImRaii.TabBar("QuestionableConfigTabs");
+        if (!tabBar)
+            return;
+
+        DrawGeneralTab();
+        DrawNotificationsTab();
+        DrawAdvancedTab();
+    }
+
+    private void DrawGeneralTab()
+    {
+        using var tab = ImRaii.TabItem("General");
+        if (!tab)
+            return;
+        int selectedMount = Array.FindIndex(_mountIds, x => x == _configuration.General.MountId);
+        if (selectedMount == -1)
         {
-            if (ImGui.BeginTabItem("General"))
-            {
-                int selectedMount = Array.FindIndex(_mountIds, x => x == _configuration.General.MountId);
-                if (selectedMount == -1)
-                {
-                    selectedMount = 0;
-                    _configuration.General.MountId = _mountIds[selectedMount];
-                    Save();
-                }
+            selectedMount = 0;
+            _configuration.General.MountId = _mountIds[selectedMount];
+            Save();
+        }
 
-                if (ImGui.Combo("Preferred Mount", ref selectedMount, _mountNames, _mountNames.Length))
-                {
-                    _configuration.General.MountId = _mountIds[selectedMount];
-                    Save();
-                }
+        if (ImGui.Combo("Preferred Mount", ref selectedMount, _mountNames, _mountNames.Length))
+        {
+            _configuration.General.MountId = _mountIds[selectedMount];
+            Save();
+        }
 
-                int grandCompany = (int)_configuration.General.GrandCompany;
-                if (ImGui.Combo("Preferred Grand Company", ref grandCompany, _grandCompanyNames,
-                        _grandCompanyNames.Length))
-                {
-                    _configuration.General.GrandCompany = (GrandCompany)grandCompany;
-                    Save();
-                }
+        int grandCompany = (int)_configuration.General.GrandCompany;
+        if (ImGui.Combo("Preferred Grand Company", ref grandCompany, _grandCompanyNames,
+                _grandCompanyNames.Length))
+        {
+            _configuration.General.GrandCompany = (GrandCompany)grandCompany;
+            Save();
+        }
 
-                bool hideInAllInstances = _configuration.General.HideInAllInstances;
-                if (ImGui.Checkbox("Hide quest window in all instanced duties", ref hideInAllInstances))
-                {
-                    _configuration.General.HideInAllInstances = hideInAllInstances;
-                    Save();
-                }
+        bool hideInAllInstances = _configuration.General.HideInAllInstances;
+        if (ImGui.Checkbox("Hide quest window in all instanced duties", ref hideInAllInstances))
+        {
+            _configuration.General.HideInAllInstances = hideInAllInstances;
+            Save();
+        }
 
-                bool useEscToCancelQuesting = _configuration.General.UseEscToCancelQuesting;
-                if (ImGui.Checkbox("Use ESC to cancel questing/movement", ref useEscToCancelQuesting))
-                {
-                    _configuration.General.UseEscToCancelQuesting = useEscToCancelQuesting;
-                    Save();
-                }
+        bool useEscToCancelQuesting = _configuration.General.UseEscToCancelQuesting;
+        if (ImGui.Checkbox("Use ESC to cancel questing/movement", ref useEscToCancelQuesting))
+        {
+            _configuration.General.UseEscToCancelQuesting = useEscToCancelQuesting;
+            Save();
+        }
 
-                bool showIncompleteSeasonalEvents = _configuration.General.ShowIncompleteSeasonalEvents;
-                if (ImGui.Checkbox("Show details for incomplete seasonal events", ref showIncompleteSeasonalEvents))
-                {
-                    _configuration.General.ShowIncompleteSeasonalEvents = showIncompleteSeasonalEvents;
-                    Save();
-                }
+        bool showIncompleteSeasonalEvents = _configuration.General.ShowIncompleteSeasonalEvents;
+        if (ImGui.Checkbox("Show details for incomplete seasonal events", ref showIncompleteSeasonalEvents))
+        {
+            _configuration.General.ShowIncompleteSeasonalEvents = showIncompleteSeasonalEvents;
+            Save();
+        }
+
+        bool configureTextAdvance = _configuration.General.ConfigureTextAdvance;
+        if (ImGui.Checkbox("Automatically configure TextAdvance with the recommended settings",
+                ref configureTextAdvance))
+        {
+            _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 var tab = ImRaii.TabItem("Notifications");
+        if (!tab)
+            return;
 
-                bool configureTextAdvance = _configuration.General.ConfigureTextAdvance;
-                if (ImGui.Checkbox("Automatically configure TextAdvance with the recommended settings", ref configureTextAdvance))
+        bool enabled = _configuration.Notifications.Enabled;
+        if (ImGui.Checkbox("Enable notifications when manual interaction is required", ref enabled))
+        {
+            _configuration.Notifications.Enabled = enabled;
+            Save();
+        }
+
+        using (ImRaii.Disabled(!_configuration.Notifications.Enabled))
+        {
+            using (ImRaii.PushIndent())
+            {
+                var xivChatTypes = Enum.GetValues<XivChatType>()
+                    .Where(x => x != XivChatType.StandardEmote)
+                    .ToArray();
+                var selectedChatType = Array.IndexOf(xivChatTypes, _configuration.Notifications.ChatType);
+                string[] chatTypeNames = xivChatTypes
+                    .Select(t => t.GetAttribute<XivChatTypeInfoAttribute>()?.FancyName ?? t.ToString())
+                    .ToArray();
+                if (ImGui.Combo("Chat channel", ref selectedChatType, chatTypeNames,
+                        chatTypeNames.Length))
                 {
-                    _configuration.General.ConfigureTextAdvance = configureTextAdvance;
+                    _configuration.Notifications.ChatType = xivChatTypes[selectedChatType];
                     Save();
                 }
 
-                if (ImGui.CollapsingHeader("Cheats"))
+                ImGui.Separator();
+                ImGui.Text("NotificationMaster settings");
+                ImGui.SameLine();
+                ImGuiComponents.HelpMarker("Requires the plugin 'NotificationMaster' to be installed.");
+                using (ImRaii.Disabled(!_notificationMasterIpc.Enabled))
                 {
-                    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))
+                    bool showTrayMessage = _configuration.Notifications.ShowTrayMessage;
+                    if (ImGui.Checkbox("Show tray notification", ref showTrayMessage))
                     {
-                        _configuration.General.AutomaticallyCompleteSnipeTasks = automaticallyCompleteSnipeTasks;
+                        _configuration.Notifications.ShowTrayMessage = showTrayMessage;
                         Save();
                     }
-                }
 
-                ImGui.EndTabItem();
+                    bool flashTaskbar = _configuration.Notifications.FlashTaskbar;
+                    if (ImGui.Checkbox("Flash taskbar icon", ref flashTaskbar))
+                    {
+                        _configuration.Notifications.FlashTaskbar = flashTaskbar;
+                        Save();
+                    }
+                }
             }
+        }
+    }
 
-            if (ImGui.BeginTabItem("Advanced"))
-            {
-                ImGui.TextColored(ImGuiColors.DalamudRed,
-                    "Enabling any option here may cause unexpected behavior. Use at your own risk.");
-
-                ImGui.Separator();
+    private void DrawAdvancedTab()
+    {
+        using var tab = ImRaii.TabItem("Advanced");
+        if (!tab)
+            return;
 
-                bool debugOverlay = _configuration.Advanced.DebugOverlay;
-                if (ImGui.Checkbox("Enable debug overlay", ref debugOverlay))
-                {
-                    _configuration.Advanced.DebugOverlay = debugOverlay;
-                    Save();
-                }
+        ImGui.TextColored(ImGuiColors.DalamudRed,
+            "Enabling any option here may cause unexpected behavior. Use at your own risk.");
 
-                bool neverFly = _configuration.Advanced.NeverFly;
-                if (ImGui.Checkbox("Disable flying (even if unlocked for the zone)", ref neverFly))
-                {
-                    _configuration.Advanced.NeverFly = neverFly;
-                    Save();
-                }
+        ImGui.Separator();
 
-                bool additionalStatusInformation = _configuration.Advanced.AdditionalStatusInformation;
-                if (ImGui.Checkbox("Draw additional status information", ref additionalStatusInformation))
-                {
-                    _configuration.Advanced.AdditionalStatusInformation = additionalStatusInformation;
-                    Save();
-                }
+        bool debugOverlay = _configuration.Advanced.DebugOverlay;
+        if (ImGui.Checkbox("Enable debug overlay", ref debugOverlay))
+        {
+            _configuration.Advanced.DebugOverlay = debugOverlay;
+            Save();
+        }
 
-                ImGui.EndTabItem();
-            }
+        bool neverFly = _configuration.Advanced.NeverFly;
+        if (ImGui.Checkbox("Disable flying (even if unlocked for the zone)", ref neverFly))
+        {
+            _configuration.Advanced.NeverFly = neverFly;
+            Save();
+        }
 
-            ImGui.EndTabBar();
+        bool additionalStatusInformation = _configuration.Advanced.AdditionalStatusInformation;
+        if (ImGui.Checkbox("Draw additional status information", ref additionalStatusInformation))
+        {
+            _configuration.Advanced.AdditionalStatusInformation = additionalStatusInformation;
+            Save();
         }
+
+        ImGui.EndTabItem();
     }
 
     private void Save() => _pluginInterface.SavePluginConfig(_configuration);
index 6026bafef205a77ad6543192a8f9e7cef372b55c..2ec22702b20a5f0f19f34da3b506a86a2616b92b 100644 (file)
@@ -44,6 +44,12 @@ internal sealed class OneTimeSetupWindow : LWindow, IDisposable
             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 Configuration _configuration;
@@ -109,6 +115,7 @@ internal sealed class OneTimeSetupWindow : LWindow, IDisposable
             {
                 if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Check, "Finish Setup"))
                 {
+                    _logger.LogInformation("Marking setup as complete");
                     _configuration.MarkPluginSetupComplete();
                     _pluginInterface.SavePluginConfig(_configuration);
                     IsOpen = false;
@@ -128,6 +135,7 @@ internal sealed class OneTimeSetupWindow : LWindow, IDisposable
 
         if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Times, "Close window & don't enable Questionable"))
         {
+            _logger.LogWarning("Closing window without all required plugins installed");
             IsOpen = false;
         }
     }
index 07f5b16f27c3cf8a4d4dcc7b0eb3f587058bca0c..eb9b1d2ca6ba33c59da8b7022a5647505b879f11 100644 (file)
       "gatheringpaths": {
         "type": "Project",
         "dependencies": {
-          "Questionable.Model": "[3.10.0, )"
+          "Questionable.Model": "[3.12.0, )"
         }
       },
       "llib": {
           "DalamudPackager": "[2.1.13, )"
         }
       },
+      "notificationmasterapi": {
+        "type": "Project"
+      },
       "questionable.model": {
         "type": "Project",
         "dependencies": {
       "questpaths": {
         "type": "Project",
         "dependencies": {
-          "Questionable.Model": "[3.10.0, )"
+          "Questionable.Model": "[3.12.0, )"
         }
       }
     }
diff --git a/vendor/NotificationMasterAPI b/vendor/NotificationMasterAPI
new file mode 160000 (submodule)
index 0000000..05b1ba7
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit 05b1ba788d5cb940ed8e82599eb88778c9cecdb0