diff --git a/.gitmodules b/.gitmodules
index 0bc08a36..832da345 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -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
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 9b59f0ef..cff821d5 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -1,5 +1,5 @@
- 3.12
+ 4.4
diff --git a/GatheringPathRenderer/EditorCommands.cs b/GatheringPathRenderer/EditorCommands.cs
index 2e9edad5..ae0247ae 100644
--- a/GatheringPathRenderer/EditorCommands.cs
+++ b/GatheringPathRenderer/EditorCommands.cs
@@ -3,16 +3,12 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
-using System.Text.Json;
-using System.Text.Json.Nodes;
-using System.Text.Json.Serialization;
-using System.Text.Json.Serialization.Metadata;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Command;
using Dalamud.Plugin.Services;
-using Lumina.Excel.GeneratedSheets;
+using Lumina.Excel.Sheets;
using Questionable.Model;
using Questionable.Model.Gathering;
using Questionable.Model.Questing;
@@ -70,14 +66,14 @@ internal sealed class EditorCommands : IDisposable
if (target == null || target.ObjectKind != ObjectKind.GatheringPoint)
throw new Exception("No valid target");
- var gatheringPoint = _dataManager.GetExcelSheet()!.GetRow(target.DataId);
+ var gatheringPoint = _dataManager.GetExcelSheet().GetRowOrDefault(target.DataId);
if (gatheringPoint == null)
throw new Exception("Invalid gathering point");
FileInfo targetFile;
GatheringRoot root;
var locationsInTerritory = _plugin.GetLocationsInTerritory(_clientState.TerritoryType).ToList();
- var location = locationsInTerritory.SingleOrDefault(x => x.Id == gatheringPoint.GatheringPointBase.Row);
+ var location = locationsInTerritory.SingleOrDefault(x => x.Id == gatheringPoint.Value.GatheringPointBase.RowId);
if (location != null)
{
targetFile = location.File;
@@ -96,7 +92,7 @@ internal sealed class EditorCommands : IDisposable
}
else
{
- (targetFile, root) = CreateNewFile(gatheringPoint, target);
+ (targetFile, root) = CreateNewFile(gatheringPoint.Value, target);
_chatGui.Print($"Creating new file under {targetFile.FullName}", "qG");
}
@@ -174,16 +170,16 @@ internal sealed class EditorCommands : IDisposable
?.File.Directory;
if (targetFolder == null)
{
- var territoryInfo = _dataManager.GetExcelSheet()!.GetRow(_clientState.TerritoryType)!;
+ var territoryInfo = _dataManager.GetExcelSheet().GetRow(_clientState.TerritoryType);
targetFolder = _plugin.PathsDirectory
- .CreateSubdirectory(ExpansionData.ExpansionFolders[(EExpansionVersion)territoryInfo.ExVersion.Row])
- .CreateSubdirectory(territoryInfo.PlaceName.Value!.Name.ToString());
+ .CreateSubdirectory(ExpansionData.ExpansionFolders[(EExpansionVersion)territoryInfo.ExVersion.RowId])
+ .CreateSubdirectory(territoryInfo.PlaceName.Value.Name.ToString());
}
FileInfo targetFile =
new FileInfo(
Path.Combine(targetFolder.FullName,
- $"{gatheringPoint.GatheringPointBase.Row}_{gatheringPoint.PlaceName.Value!.Name}_{(_clientState.LocalPlayer!.ClassJob.Id == 16 ? "MIN" : "BTN")}.json"));
+ $"{gatheringPoint.GatheringPointBase.RowId}_{gatheringPoint.PlaceName.Value.Name}_{(_clientState.LocalPlayer!.ClassJob.RowId == 16 ? "MIN" : "BTN")}.json"));
var root = new GatheringRoot
{
Author = [_configuration.AuthorName],
diff --git a/GatheringPathRenderer/GatheringPathRenderer.csproj b/GatheringPathRenderer/GatheringPathRenderer.csproj
index fc157f20..6009a518 100644
--- a/GatheringPathRenderer/GatheringPathRenderer.csproj
+++ b/GatheringPathRenderer/GatheringPathRenderer.csproj
@@ -1,4 +1,4 @@
-
+
diff --git a/GatheringPathRenderer/RendererPlugin.cs b/GatheringPathRenderer/RendererPlugin.cs
index dbad1715..5ef430ea 100644
--- a/GatheringPathRenderer/RendererPlugin.cs
+++ b/GatheringPathRenderer/RendererPlugin.cs
@@ -59,7 +59,7 @@ public sealed class RendererPlugin : IDalamudPlugin
_editorWindow = new EditorWindow(this, _editorCommands, dataManager, targetManager, clientState, objectTable)
{ IsOpen = true };
_windowSystem.AddWindow(_editorWindow);
- _currentClassJob = (EClassJob?)_clientState.LocalPlayer?.ClassJob.Id ?? EClassJob.Adventurer;
+ _currentClassJob = (EClassJob?)_clientState.LocalPlayer?.ClassJob.RowId ?? EClassJob.Adventurer;
_pluginInterface.GetIpcSubscriber
diff --git a/Questionable/QuestionablePlugin.cs b/Questionable/QuestionablePlugin.cs
index bac54241..8cc59d16 100644
--- a/Questionable/QuestionablePlugin.cs
+++ b/Questionable/QuestionablePlugin.cs
@@ -6,6 +6,7 @@ using Dalamud.Game.ClientState.Objects;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
+using LLib;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Questionable.Controller;
@@ -110,7 +111,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
- serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
@@ -125,6 +126,8 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
}
private static void AddTaskFactories(ServiceCollection serviceCollection)
@@ -134,7 +137,8 @@ public sealed class QuestionablePlugin : IDalamudPlugin
.AddTaskExecutor();
serviceCollection.AddTaskExecutor();
serviceCollection.AddTaskExecutor();
- serviceCollection.AddTaskExecutor();
+ serviceCollection.AddTaskFactoryAndExecutor();
serviceCollection.AddTaskExecutor();
serviceCollection.AddTaskExecutor();
@@ -143,7 +147,6 @@ public sealed class QuestionablePlugin : IDalamudPlugin
.AddTaskFactoryAndExecutor();
serviceCollection.AddTaskFactory();
- serviceCollection.AddTaskFactoryAndExecutor();
serviceCollection.AddTaskExecutor();
serviceCollection
.AddTaskFactoryAndExecutor();
serviceCollection
.AddTaskFactoryAndExecutor();
+ serviceCollection.AddTaskFactoryAndExecutor();
+ serviceCollection.AddTaskExecutor();
serviceCollection
.AddTaskFactoryAndExecutor();
@@ -160,6 +165,8 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddTaskFactoryAndExecutor();
serviceCollection.AddTaskExecutor();
serviceCollection.AddTaskExecutor();
+ serviceCollection
+ .AddTaskFactoryAndExecutor();
serviceCollection
.AddTaskFactoryAndExecutor();
@@ -255,6 +262,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
@@ -292,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 0416943f..27c968a0 100644
--- a/Questionable/Windows/ConfigWindow.cs
+++ b/Questionable/Windows/ConfigWindow.cs
@@ -1,12 +1,18 @@
using System;
+using System.Collections.Generic;
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 Lumina.Excel.Sheets;
+using Questionable.External;
using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany;
namespace Questionable.Windows;
@@ -14,6 +20,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,13 +30,14 @@ 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()!
+ var mounts = dataManager.GetExcelSheet()
.Where(x => x is { RowId: > 0, Icon: > 0 })
.Select(x => (MountId: x.RowId, Name: x.Singular.ToString()))
.Where(x => !string.IsNullOrEmpty(x.Name))
@@ -43,109 +51,162 @@ 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"))
+ selectedMount = 0;
+ _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();
+ }
+
+ 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 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();
+ }
+ }
+
+ private void DrawNotificationsTab()
+ {
+ using var tab = ImRaii.TabItem("Notifications");
+ if (!tab)
+ return;
+
+ 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())
{
- int selectedMount = Array.FindIndex(_mountIds, x => x == _configuration.General.MountId);
- if (selectedMount == -1)
+ var xivChatTypes = Enum.GetValues()
+ .Where(x => x != XivChatType.StandardEmote)
+ .ToArray();
+ var selectedChatType = Array.IndexOf(xivChatTypes, _configuration.Notifications.ChatType);
+ string[] chatTypeNames = xivChatTypes
+ .Select(t => t.GetAttribute()?.FancyName ?? t.ToString())
+ .ToArray();
+ if (ImGui.Combo("Chat channel", ref selectedChatType, chatTypeNames,
+ chatTypeNames.Length))
{
- selectedMount = 0;
- _configuration.General.MountId = _mountIds[selectedMount];
+ _configuration.Notifications.ChatType = xivChatTypes[selectedChatType];
Save();
}
- if (ImGui.Combo("Preferred Mount", ref selectedMount, _mountNames, _mountNames.Length))
+ ImGui.Separator();
+ ImGui.Text("NotificationMaster settings");
+ ImGui.SameLine();
+ ImGuiComponents.HelpMarker("Requires the plugin 'NotificationMaster' to be installed.");
+ using (ImRaii.Disabled(!_notificationMasterIpc.Enabled))
{
- _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();
- }
-
- 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 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))
+ bool showTrayMessage = _configuration.Notifications.ShowTrayMessage;
+ if (ImGui.Checkbox("Show tray notification", ref showTrayMessage))
{
- _configuration.General.AutomaticallyCompleteSnipeTasks = automaticallyCompleteSnipeTasks;
+ _configuration.Notifications.ShowTrayMessage = showTrayMessage;
+ Save();
+ }
+
+ bool flashTaskbar = _configuration.Notifications.FlashTaskbar;
+ if (ImGui.Checkbox("Flash taskbar icon", ref flashTaskbar))
+ {
+ _configuration.Notifications.FlashTaskbar = flashTaskbar;
Save();
}
}
-
- ImGui.EndTabItem();
}
-
- if (ImGui.BeginTabItem("Advanced"))
- {
- ImGui.TextColored(ImGuiColors.DalamudRed,
- "Enabling any option here may cause unexpected behavior. Use at your own risk.");
-
- ImGui.Separator();
-
- bool debugOverlay = _configuration.Advanced.DebugOverlay;
- if (ImGui.Checkbox("Enable debug overlay", ref debugOverlay))
- {
- _configuration.Advanced.DebugOverlay = debugOverlay;
- Save();
- }
-
- bool neverFly = _configuration.Advanced.NeverFly;
- if (ImGui.Checkbox("Disable flying (even if unlocked for the zone)", ref neverFly))
- {
- _configuration.Advanced.NeverFly = neverFly;
- Save();
- }
-
- bool additionalStatusInformation = _configuration.Advanced.AdditionalStatusInformation;
- if (ImGui.Checkbox("Draw additional status information", ref additionalStatusInformation))
- {
- _configuration.Advanced.AdditionalStatusInformation = additionalStatusInformation;
- Save();
- }
-
- ImGui.EndTabItem();
- }
-
- ImGui.EndTabBar();
}
}
+ private void DrawAdvancedTab()
+ {
+ using var tab = ImRaii.TabItem("Advanced");
+ if (!tab)
+ return;
+
+ ImGui.TextColored(ImGuiColors.DalamudRed,
+ "Enabling any option here may cause unexpected behavior. Use at your own risk.");
+
+ ImGui.Separator();
+
+ bool debugOverlay = _configuration.Advanced.DebugOverlay;
+ if (ImGui.Checkbox("Enable debug overlay", ref debugOverlay))
+ {
+ _configuration.Advanced.DebugOverlay = debugOverlay;
+ Save();
+ }
+
+ bool neverFly = _configuration.Advanced.NeverFly;
+ if (ImGui.Checkbox("Disable flying (even if unlocked for the zone)", ref neverFly))
+ {
+ _configuration.Advanced.NeverFly = neverFly;
+ Save();
+ }
+
+ 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);
public void SaveWindowConfig() => Save();
diff --git a/Questionable/Windows/JournalComponents/GatheringJournalComponent.cs b/Questionable/Windows/JournalComponents/GatheringJournalComponent.cs
index 81588465..daac30a4 100644
--- a/Questionable/Windows/JournalComponents/GatheringJournalComponent.cs
+++ b/Questionable/Windows/JournalComponents/GatheringJournalComponent.cs
@@ -10,7 +10,7 @@ using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures;
using ImGuiNET;
using LLib.GameData;
-using Lumina.Excel.GeneratedSheets;
+using Lumina.Excel.Sheets;
using Questionable.Controller;
using Questionable.Model;
using Questionable.Model.Gathering;
@@ -44,24 +44,24 @@ internal sealed class GatheringJournalComponent
_gatheringPointRegistry = gatheringPointRegistry;
// TODO some of the logic here would be better suited elsewhere, in particular the [item] → [gathering item] → [location] lookup
- var routeToGatheringPoint = dataManager.GetExcelSheet()!
- .Where(x => x.UnkData0[0].GatheringPoint != 0)
- .SelectMany(x => x.UnkData0
- .Where(y => y.GatheringPoint != 0)
+ var routeToGatheringPoint = dataManager.GetExcelSheet()
+ .Where(x => x.GatheringPoint[0].RowId != 0)
+ .SelectMany(x => x.GatheringPoint
+ .Where(y => y.RowId != 0)
.Select(y => new
{
RouteId = x.RowId,
- GatheringPointId = y.GatheringPoint
+ GatheringPointId = y.RowId
}))
.GroupBy(x => x.RouteId)
.ToDictionary(x => x.Key, x => x.Select(y => y.GatheringPointId).ToList());
- var gatheringLeveSheet = dataManager.GetExcelSheet()!;
- var territoryTypeSheet = dataManager.GetExcelSheet()!;
- var gatheringPointToLeve = dataManager.GetExcelSheet()!
+ var gatheringLeveSheet = dataManager.GetExcelSheet();
+ var territoryTypeSheet = dataManager.GetExcelSheet();
+ var gatheringPointToLeve = dataManager.GetExcelSheet()
.Where(x => x.RowId > 0)
.Select(x =>
{
- uint startZonePlaceName = x.PlaceNameStartZone.Row;
+ uint startZonePlaceName = x.PlaceNameStartZone.RowId;
startZonePlaceName = startZonePlaceName switch
{
27 => 28, // limsa
@@ -71,16 +71,17 @@ internal sealed class GatheringJournalComponent
_ => startZonePlaceName
};
- var territoryType = territoryTypeSheet.FirstOrDefault(y => startZonePlaceName == y.PlaceName.Row)
+ var territoryType = territoryTypeSheet.Cast()
+ .FirstOrDefault(y => startZonePlaceName == y!.Value.PlaceName.RowId)
?? throw new InvalidOperationException($"Unable to use {startZonePlaceName}");
return new
{
LeveId = x.RowId,
LeveName = x.Name.ToString(),
TerritoryType = (ushort)territoryType.RowId,
- TerritoryName = territoryType.PlaceName.Value?.Name.ToString(),
- Expansion = (EExpansionVersion)territoryType.ExVersion.Row,
- GatheringLeve = gatheringLeveSheet.GetRow((uint)x.DataId),
+ TerritoryName = territoryType.PlaceName.ValueNullable?.Name.ToString(),
+ Expansion = (EExpansionVersion)territoryType.ExVersion.RowId,
+ GatheringLeve = gatheringLeveSheet.GetRowOrDefault(x.DataId.RowId),
};
})
.Where(x => x.GatheringLeve != null)
@@ -91,9 +92,9 @@ internal sealed class GatheringJournalComponent
x.TerritoryType,
x.TerritoryName,
x.Expansion,
- GatheringPoints = x.GatheringLeve!.Route
- .Where(y => y.Row != 0)
- .SelectMany(y => routeToGatheringPoint[y.Row]),
+ GatheringPoints = x.GatheringLeve!.Value.Route
+ .Where(y => y.RowId != 0)
+ .SelectMany(y => routeToGatheringPoint[y.RowId]),
})
.SelectMany(x => x.GatheringPoints.Select(y => new
{
@@ -107,43 +108,43 @@ internal sealed class GatheringJournalComponent
.GroupBy(x => x.GatheringPointId)
.ToDictionary(x => x.Key, x => x.First());
- var itemSheet = dataManager.GetExcelSheet- ()!;
+ var itemSheet = dataManager.GetExcelSheet
- ();
- _gatheringItems = dataManager.GetExcelSheet()!
- .Where(x => x.RowId != 0 && x.GatheringItemLevel.Row != 0)
+ _gatheringItems = dataManager.GetExcelSheet()
+ .Where(x => x.RowId != 0 && x.GatheringItemLevel.RowId != 0)
.Select(x => new
{
GatheringItemId = (int)x.RowId,
- Name = itemSheet.GetRow((uint)x.Item)?.Name.ToString()
+ Name = itemSheet.GetRowOrDefault(x.Item.RowId)?.Name.ToString()
})
.Where(x => !string.IsNullOrEmpty(x.Name))
.ToDictionary(x => x.GatheringItemId, x => x.Name!);
- _gatheringPointsByExpansion = dataManager.GetExcelSheet()!
- .Where(x => x.GatheringPointBase.Row != 0)
- .Where(x => x.GatheringPointBase.Row is < 653 or > 680) // exclude ishgard restoration phase 1
- .DistinctBy(x => x.GatheringPointBase.Row)
+ _gatheringPointsByExpansion = dataManager.GetExcelSheet()
+ .Where(x => x.GatheringPointBase.RowId != 0)
+ .Where(x => x.GatheringPointBase.RowId is < 653 or > 680) // exclude ishgard restoration phase 1
+ .DistinctBy(x => x.GatheringPointBase.RowId)
.Select(x => new
{
GatheringPointId = x.RowId,
- Point = new DefaultGatheringPoint(new GatheringPointId((ushort)x.GatheringPointBase.Row),
- x.GatheringPointBase.Value!.GatheringType.Row switch
+ Point = new DefaultGatheringPoint(new GatheringPointId((ushort)x.GatheringPointBase.RowId),
+ x.GatheringPointBase.Value.GatheringType.RowId switch
{
0 or 1 => EClassJob.Miner,
2 or 3 => EClassJob.Botanist,
_ => EClassJob.Fisher
},
x.GatheringPointBase.Value.GatheringLevel,
- x.GatheringPointBase.Value.Item.Where(y => y != 0).Select(y => (ushort)y).ToList(),
- (EExpansionVersion?)x.TerritoryType.Value?.ExVersion.Row ?? (EExpansionVersion)byte.MaxValue,
- (ushort)x.TerritoryType.Row,
- x.TerritoryType.Value?.PlaceName.Value?.Name.ToString(),
- $"{x.GatheringPointBase.Row} - {x.PlaceName.Value?.Name}")
+ x.GatheringPointBase.Value.Item.Where(y => y.RowId != 0).Select(y => (ushort)y.RowId).ToList(),
+ (EExpansionVersion?)x.TerritoryType.ValueNullable?.ExVersion.RowId ?? (EExpansionVersion)byte.MaxValue,
+ (ushort)x.TerritoryType.RowId,
+ x.TerritoryType.ValueNullable?.PlaceName.ValueNullable?.Name.ToString(),
+ $"{x.GatheringPointBase.RowId} - {x.PlaceName.ValueNullable?.Name}")
})
.Where(x => x.Point.ClassJob != EClassJob.Fisher)
.Select(x =>
{
- if (gatheringPointToLeve.TryGetValue((int)x.GatheringPointId, out var leve))
+ if (gatheringPointToLeve.TryGetValue(x.GatheringPointId, out var leve))
{
// it's a leve
return x.Point with
@@ -158,12 +159,12 @@ internal sealed class GatheringJournalComponent
_gatheringPointRegistry.TryGetGatheringPoint(x.Point.Id, out GatheringRoot? gatheringRoot))
{
// for some reason the game doesn't know where this gathering location is
- var territoryType = territoryTypeSheet.GetRow(gatheringRoot.Steps.Last().TerritoryId)!;
+ var territoryType = territoryTypeSheet.GetRow(gatheringRoot.Steps.Last().TerritoryId);
return x.Point with
{
- Expansion = (EExpansionVersion)territoryType.ExVersion.Row,
+ Expansion = (EExpansionVersion)territoryType.ExVersion.RowId,
TerritoryType = (ushort)territoryType.RowId,
- TerritoryName = territoryType.PlaceName.Value?.Name.ToString(),
+ TerritoryName = territoryType.PlaceName.ValueNullable?.Name.ToString(),
};
}
else
@@ -429,7 +430,7 @@ internal sealed class GatheringJournalComponent
}
}
- public void ClearCounts()
+ public void ClearCounts(int type, int code)
{
foreach (var expansion in _gatheringPointsByExpansion)
{
diff --git a/Questionable/Windows/JournalComponents/QuestJournalComponent.cs b/Questionable/Windows/JournalComponents/QuestJournalComponent.cs
index f273b00d..36565fdf 100644
--- a/Questionable/Windows/JournalComponents/QuestJournalComponent.cs
+++ b/Questionable/Windows/JournalComponents/QuestJournalComponent.cs
@@ -368,7 +368,7 @@ internal sealed class QuestJournalComponent
}
}
- internal void ClearCounts()
+ internal void ClearCounts(int type, int code)
{
foreach (var genreCount in _genreCounts.ToList())
_genreCounts[genreCount.Key] = genreCount.Value with { Completed = 0 };
diff --git a/Questionable/Windows/OneTimeSetupWindow.cs b/Questionable/Windows/OneTimeSetupWindow.cs
new file mode 100644
index 00000000..f1c71552
--- /dev/null
+++ b/Questionable/Windows/OneTimeSetupWindow.cs
@@ -0,0 +1,210 @@
+using System;
+using System.Collections.Generic;
+using Dalamud.Interface;
+using Dalamud.Interface.Colors;
+using Dalamud.Interface.Components;
+using Dalamud.Interface.Utility.Raii;
+using Dalamud.Plugin;
+using Dalamud.Utility;
+using ImGuiNET;
+using LLib;
+using LLib.ImGui;
+using Microsoft.Extensions.Logging;
+using Questionable.External;
+
+namespace Questionable.Windows;
+
+internal sealed class OneTimeSetupWindow : LWindow
+{
+ private static readonly IReadOnlyList RequiredPlugins =
+ [
+ new("vnavmesh",
+ """
+ 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://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/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/MyDalamudPlugins/raw/main/pluginmaster.json")),
+ ];
+
+ private readonly IReadOnlyList _recommendedPlugins;
+
+ private readonly Configuration _configuration;
+ private readonly IDalamudPluginInterface _pluginInterface;
+ private readonly UiUtils _uiUtils;
+ private readonly DalamudReflector _dalamudReflector;
+ private readonly ILogger _logger;
+
+ public OneTimeSetupWindow(Configuration configuration, IDalamudPluginInterface pluginInterface, UiUtils uiUtils,
+ DalamudReflector dalamudReflector, ILogger logger, AutomatonIpc automatonIpc)
+ : base("Questionable Setup###QuestionableOneTimeSetup",
+ ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoSavedSettings, true)
+ {
+ _configuration = configuration;
+ _pluginInterface = pluginInterface;
+ _uiUtils = uiUtils;
+ _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;
+ AllowClickthrough = false;
+ IsOpen = !_configuration.IsPluginSetupComplete();
+ _logger.LogInformation("One-time setup needed: {IsOpen}", IsOpen);
+ }
+
+ public override void Draw()
+ {
+ 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:");
+ bool allRequiredInstalled = true;
+ using (ImRaii.PushIndent())
+ {
+ foreach (var plugin in RequiredPlugins)
+ allRequiredInstalled &= DrawPlugin(plugin, checklistPadding);
+ }
+
+ ImGui.Spacing();
+ ImGui.Separator();
+ ImGui.Spacing();
+
+ ImGui.Text("The following plugins are recommended, but not required:");
+ using (ImRaii.PushIndent())
+ {
+ foreach (var plugin in _recommendedPlugins)
+ DrawPlugin(plugin, checklistPadding);
+ }
+
+ ImGui.Spacing();
+ ImGui.Separator();
+ ImGui.Spacing();
+
+ if (allRequiredInstalled)
+ {
+ using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.ParsedGreen))
+ {
+ if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Check, "Finish Setup"))
+ {
+ _logger.LogInformation("Marking setup as complete");
+ _configuration.MarkPluginSetupComplete();
+ _pluginInterface.SavePluginConfig(_configuration);
+ IsOpen = false;
+ }
+ }
+ }
+ else
+ {
+ using (ImRaii.Disabled())
+ {
+ using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed))
+ ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Check, "Missing required plugins");
+ }
+ }
+
+ ImGui.SameLine();
+
+ if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Times, "Close window & don't enable Questionable"))
+ {
+ _logger.LogWarning("Closing window without all required plugins installed");
+ IsOpen = false;
+ }
+ }
+
+ private bool DrawPlugin(PluginInfo plugin, float checklistPadding)
+ {
+ bool isInstalled = IsPluginInstalled(plugin.DisplayName);
+ using (ImRaii.PushId("plugin_" + plugin.DisplayName))
+ {
+ _uiUtils.ChecklistItem(plugin.DisplayName, isInstalled);
+ using (ImRaii.PushIndent(checklistPadding))
+ {
+ ImGui.TextUnformatted(plugin.Details);
+ 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 isInstalled;
+ }
+
+ private bool IsPluginInstalled(string internalName)
+ {
+ return _dalamudReflector.TryGetDalamudPlugin(internalName, out _, suppressErrors: true, ignoreCache: true);
+ }
+
+ private sealed record PluginInfo(
+ string DisplayName,
+ string Details,
+ Uri WebsiteUri,
+ Uri? DalamudRepositoryUri,
+ List? DetailsToCheck = null);
+
+ private sealed record PluginDetailInfo(string DisplayName, Func Predicate);
+}
diff --git a/Questionable/Windows/QuestComponents/ActiveQuestComponent.cs b/Questionable/Windows/QuestComponents/ActiveQuestComponent.cs
index 34beb090..2652685f 100644
--- a/Questionable/Windows/QuestComponents/ActiveQuestComponent.cs
+++ b/Questionable/Windows/QuestComponents/ActiveQuestComponent.cs
@@ -128,13 +128,13 @@ internal sealed partial class ActiveQuestComponent
{
using var _ = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
ImGui.TextUnformatted(
- $"Simulated Quest: {Shorten(currentQuest.Quest.Info.Name)} / {currentQuest.Sequence} / {currentQuest.Step}");
+ $"Simulated Quest: {Shorten(currentQuest.Quest.Info.Name)} ({currentQuest.Quest.Id}) / {currentQuest.Sequence} / {currentQuest.Step}");
}
else if (currentQuestType == QuestController.ECurrentQuestType.Gathering)
{
using var _ = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.ParsedGold);
ImGui.TextUnformatted(
- $"Gathering: {Shorten(currentQuest.Quest.Info.Name)} / {currentQuest.Sequence} / {currentQuest.Step}");
+ $"Gathering: {Shorten(currentQuest.Quest.Info.Name)} ({currentQuest.Quest.Id}) / {currentQuest.Sequence} / {currentQuest.Step}");
}
else
{
@@ -154,7 +154,7 @@ internal sealed partial class ActiveQuestComponent
}
ImGui.TextUnformatted(
- $"Quest: {Shorten(startedQuest.Quest.Info.Name)} / {startedQuest.Sequence} / {startedQuest.Step}");
+ $"Quest: {Shorten(startedQuest.Quest.Info.Name)} ({startedQuest.Quest.Id}) / {startedQuest.Sequence} / {startedQuest.Step}");
if (startedQuest.Quest.Root.Disabled)
{
@@ -177,7 +177,7 @@ internal sealed partial class ActiveQuestComponent
{
using var _ = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow);
ImGui.TextUnformatted(
- $"Next Quest: {Shorten(nextQuest.Quest.Info.Name)} / {nextQuest.Sequence} / {nextQuest.Step}");
+ $"Next Quest: {Shorten(nextQuest.Quest.Info.Name)} ({nextQuest.Quest.Id}) / {nextQuest.Sequence} / {nextQuest.Step}");
}
}
}
@@ -400,7 +400,7 @@ internal sealed partial class ActiveQuestComponent
ImGui.SameLine();
if (ImGui.Button("Clear sim"))
{
- _questController.SimulateQuest(null);
+ _questController.SimulateQuest(null, 0, 0);
_movementController.Stop();
_questController.Stop("ClearSim");
diff --git a/Questionable/Windows/QuestComponents/EventInfoComponent.cs b/Questionable/Windows/QuestComponents/EventInfoComponent.cs
index fedeb045..d85e1f70 100644
--- a/Questionable/Windows/QuestComponents/EventInfoComponent.cs
+++ b/Questionable/Windows/QuestComponents/EventInfoComponent.cs
@@ -22,7 +22,6 @@ internal sealed class EventInfoComponent
[SuppressMessage("ReSharper", "CollectionNeverUpdated.Local")]
private readonly List _eventQuests =
[
- new("All Saints' Wake", [new(5184), new(5185)], AtDailyReset(new(2024, 11, 4))),
];
private readonly QuestData _questData;
diff --git a/Questionable/Windows/QuestWindow.cs b/Questionable/Windows/QuestWindow.cs
index 37724c8c..4c100a1b 100644
--- a/Questionable/Windows/QuestWindow.cs
+++ b/Questionable/Windows/QuestWindow.cs
@@ -67,7 +67,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
#endif
SizeConstraints = new WindowSizeConstraints
{
- MinimumSize = new Vector2(200, 30),
+ MinimumSize = new Vector2(230, 30),
MaximumSize = default
};
RespectCloseHotkey = false;
@@ -113,6 +113,9 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
public override bool DrawConditions()
{
+ if (!_configuration.IsPluginSetupComplete())
+ return false;
+
if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null || _clientState.IsPvPExcludingDen)
return false;
diff --git a/Questionable/packages.lock.json b/Questionable/packages.lock.json
index 07f5b16f..b358da6e 100644
--- a/Questionable/packages.lock.json
+++ b/Questionable/packages.lock.json
@@ -13,9 +13,9 @@
},
"DalamudPackager": {
"type": "Direct",
- "requested": "[2.1.13, )",
- "resolved": "2.1.13",
- "contentHash": "rMN1omGe8536f4xLMvx9NwfvpAc9YFFfeXJ1t4P4PE6Gu8WCIoFliR1sh07hM+bfODmesk/dvMbji7vNI+B/pQ=="
+ "requested": "[11.0.0, )",
+ "resolved": "11.0.0",
+ "contentHash": "bjT7XUlhIJSmsE/O76b7weUX+evvGQctbQB8aKXt94o+oPWxHpCepxAGMs7Thow3AzCyqWs7cOpp9/2wcgRRQA=="
},
"DotNet.ReproducibleBuilds": {
"type": "Direct",
@@ -174,15 +174,18 @@
"gatheringpaths": {
"type": "Project",
"dependencies": {
- "Questionable.Model": "[3.10.0, )"
+ "Questionable.Model": "[4.0.0, )"
}
},
"llib": {
"type": "Project",
"dependencies": {
- "DalamudPackager": "[2.1.13, )"
+ "DalamudPackager": "[11.0.0, )"
}
},
+ "notificationmasterapi": {
+ "type": "Project"
+ },
"questionable.model": {
"type": "Project",
"dependencies": {
@@ -192,7 +195,7 @@
"questpaths": {
"type": "Project",
"dependencies": {
- "Questionable.Model": "[3.10.0, )"
+ "Questionable.Model": "[4.0.0, )"
}
}
}
diff --git a/vendor/ECommons b/vendor/ECommons
index 147e12e9..6ea40a9e 160000
--- a/vendor/ECommons
+++ b/vendor/ECommons
@@ -1 +1 @@
-Subproject commit 147e12e95f2fb781f2c8ddac31d948700ed9051c
+Subproject commit 6ea40a9eea2e805f2f566fe0493749c7c0639ea3
diff --git a/vendor/NotificationMasterAPI b/vendor/NotificationMasterAPI
new file mode 160000
index 00000000..05b1ba78
--- /dev/null
+++ b/vendor/NotificationMasterAPI
@@ -0,0 +1 @@
+Subproject commit 05b1ba788d5cb940ed8e82599eb88778c9cecdb0