Indicate navmesh build % in UI
authorLiza Carvelli <liza@carvel.li>
Sun, 23 Feb 2025 17:31:51 +0000 (18:31 +0100)
committerLiza Carvelli <liza@carvel.li>
Sun, 23 Feb 2025 19:56:38 +0000 (20:56 +0100)
16 files changed:
QuestPaths/quest-v1.json
Questionable.Model/Questing/Converter/SkipConditionConverter.cs
Questionable.Model/Questing/EExtraSkipCondition.cs
Questionable.sln.DotSettings
Questionable/Controller/MovementController.cs
Questionable/Controller/QuestController.cs
Questionable/Controller/Steps/Common/WaitNavmesh.cs [new file with mode: 0644]
Questionable/Controller/Steps/Interactions/SinglePlayerDuty.cs
Questionable/Controller/Steps/Shared/AethernetShortcut.cs
Questionable/Controller/Steps/Shared/Gather.cs
Questionable/Controller/Steps/Shared/MoveTo.cs
Questionable/Controller/Steps/Shared/SkipCondition.cs
Questionable/Controller/Steps/TaskExecutor.cs
Questionable/External/NavmeshIpc.cs
Questionable/QuestionablePlugin.cs
Questionable/Windows/QuestComponents/ActiveQuestComponent.cs

index ede8e6c..595974c 100644 (file)
                     "WakingSandsSolar",
                     "RisingStonesSolar",
                     "RoguesGuild",
+                    "NotRoguesGuild",
                     "DockStorehouse"
                   ]
                 }
index eb91795..b49a1cf 100644 (file)
@@ -11,6 +11,7 @@ public sealed class SkipConditionConverter() : EnumConverter<EExtraSkipCondition
         { EExtraSkipCondition.WakingSandsSolar, "WakingSandsSolar" },
         { EExtraSkipCondition.RisingStonesSolar, "RisingStonesSolar"},
         { EExtraSkipCondition.RoguesGuild, "RoguesGuild"},
+        { EExtraSkipCondition.NotRoguesGuild, "NotRoguesGuild"},
         { EExtraSkipCondition.DockStorehouse, "DockStorehouse"},
     };
 }
index 8ec77d4..47de211 100644 (file)
@@ -15,6 +15,7 @@ public enum EExtraSkipCondition
     /// Location for ROG quests in Limsa Lominsa; located far underneath the actual lower decks.
     /// </summary>
     RoguesGuild,
+    NotRoguesGuild,
 
     /// <summary>
     /// Location for NIN quests in Eastern La Noscea; located far underneath the actual zone.
index ef6a1ab..528fc57 100644 (file)
@@ -37,6 +37,7 @@
        <s:Boolean x:Key="/Default/UserDictionary/Words/=tertium/@EntryIndexedValue">True</s:Boolean>
        <s:Boolean x:Key="/Default/UserDictionary/Words/=tural/@EntryIndexedValue">True</s:Boolean>
        <s:Boolean x:Key="/Default/UserDictionary/Words/=urqopacha/@EntryIndexedValue">True</s:Boolean>
+       <s:Boolean x:Key="/Default/UserDictionary/Words/=vnavmesh/@EntryIndexedValue">True</s:Boolean>
        <s:Boolean x:Key="/Default/UserDictionary/Words/=wachumeqimeqi/@EntryIndexedValue">True</s:Boolean>
        <s:Boolean x:Key="/Default/UserDictionary/Words/=wachunpelo/@EntryIndexedValue">True</s:Boolean>
        <s:Boolean x:Key="/Default/UserDictionary/Words/=wolekdorf/@EntryIndexedValue">True</s:Boolean>
index cc8012c..338bea9 100644 (file)
@@ -89,6 +89,7 @@ internal sealed class MovementController : IDisposable
     public bool IsPathfinding => _pathfindTask is { IsCompleted: false };
     public DestinationData? Destination { get; set; }
     public DateTime MovementStartedAt { get; private set; } = DateTime.Now;
+    public int BuiltNavmeshPercent => _navmeshIpc.GetBuildProgress();
 
     public void Update()
     {
index b509de1..c10d160 100644 (file)
@@ -687,6 +687,17 @@ internal sealed class QuestController : MiniTaskController<QuestController>
     public bool IsRunning => !_taskQueue.AllTasksComplete;
     public TaskQueue TaskQueue => _taskQueue;
 
+    public string? CurrentTaskState
+    {
+        get
+        {
+            if (_taskQueue.CurrentTaskExecutor is IDebugStateProvider debugStateProvider)
+                return debugStateProvider.GetDebugState();
+            else
+                return null;
+        }
+    }
+
     public sealed class QuestProgress
     {
         public Quest Quest { get; }
diff --git a/Questionable/Controller/Steps/Common/WaitNavmesh.cs b/Questionable/Controller/Steps/Common/WaitNavmesh.cs
new file mode 100644 (file)
index 0000000..552c768
--- /dev/null
@@ -0,0 +1,27 @@
+namespace Questionable.Controller.Steps.Common;
+
+internal sealed class WaitNavmesh
+{
+    internal sealed record Task : ITask
+    {
+        public override string ToString() => "Wait(navmesh)";
+    }
+
+    internal sealed class Executor(MovementController movementController) : TaskExecutor<Task>, IDebugStateProvider
+    {
+        protected override bool Start() => true;
+
+        public override ETaskResult Update() =>
+            movementController.IsNavmeshReady ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
+
+        public override bool ShouldInterruptOnDamage() => false;
+
+        public string? GetDebugState()
+        {
+            if (!movementController.IsNavmeshReady)
+                return $"Navmesh: {movementController.BuiltNavmeshPercent}%";
+            else
+                return null;
+        }
+    }
+}
index 7bf43b8..ef61cb4 100644 (file)
@@ -96,7 +96,9 @@ internal static class SinglePlayerDuty
     }
 
     internal sealed class WaitSinglePlayerDutyExecutor(
-        BossModIpc bossModIpc) : TaskExecutor<WaitSinglePlayerDuty>, IStoppableTaskExecutor
+        BossModIpc bossModIpc,
+        MovementController movementController)
+        : TaskExecutor<WaitSinglePlayerDuty>, IStoppableTaskExecutor, IDebugStateProvider
     {
         protected override bool Start() => true;
 
@@ -110,6 +112,14 @@ internal static class SinglePlayerDuty
         public void StopNow() => bossModIpc.DisableAi();
 
         public override bool ShouldInterruptOnDamage() => false;
+
+        public string? GetDebugState()
+        {
+            if (!movementController.IsNavmeshReady)
+                return $"Navmesh: {movementController.BuiltNavmeshPercent}%";
+            else
+                return null;
+        }
     }
 
     internal sealed record DisableAi : ITask
index 460aa44..ff1c585 100644 (file)
@@ -20,7 +20,6 @@ namespace Questionable.Controller.Steps.Shared;
 internal static class AethernetShortcut
 {
     internal sealed class Factory(
-        MovementController movementController,
         AetheryteData aetheryteData,
         TerritoryData territoryData,
         IClientState clientState)
@@ -31,8 +30,7 @@ internal static class AethernetShortcut
             if (step.AethernetShortcut == null)
                 yield break;
 
-            yield return new WaitCondition.Task(() => movementController.IsNavmeshReady,
-                "Wait(navmesh ready)");
+            yield return new WaitNavmesh.Task();
             yield return new Task(step.AethernetShortcut.From, step.AethernetShortcut.To,
                 step.SkipConditions?.AethernetShortcutIf ?? new());
 
index f4aad9c..45c007c 100644 (file)
@@ -38,7 +38,6 @@ internal static class Gather
     }
 
     internal sealed class DelayedGatheringExecutor(
-        MovementController movementController,
         GatheringData gatheringData,
         GatheringPointRegistry gatheringPointRegistry,
         TerritoryData territoryData,
@@ -85,8 +84,7 @@ internal static class Gather
             yield return new WaitCondition.Task(() => clientState.TerritoryType == territoryId,
                 $"Wait(territory: {territoryData.GetNameAndId(territoryId)})");
 
-            yield return new WaitCondition.Task(() => movementController.IsNavmeshReady,
-                "Wait(navmesh ready)");
+            yield return new WaitNavmesh.Task();
 
             yield return new GatheringTask(gatheringPointId, Task.GatheredItem);
             yield return new WaitAtEnd.WaitDelay();
index 60d83ae..741c543 100644 (file)
@@ -25,7 +25,6 @@ namespace Questionable.Controller.Steps.Shared;
 internal static class MoveTo
 {
     internal sealed class Factory(
-        MovementController movementController,
         IClientState clientState,
         AetheryteData aetheryteData,
         TerritoryData territoryData,
@@ -67,10 +66,7 @@ internal static class MoveTo
                 $"Wait(territory: {territoryData.GetNameAndId(step.TerritoryId)})");
 
             if (!step.DisableNavmesh)
-            {
-                yield return new WaitCondition.Task(() => movementController.IsNavmeshReady,
-                    "Wait(navmesh ready)");
-            }
+                yield return new WaitNavmesh.Task();
 
             yield return new MoveTask(step, destination);
 
index bd95353..b651165 100644 (file)
@@ -310,6 +310,7 @@ internal static class SkipCondition
                 EExtraSkipCondition.WakingSandsSolar => territoryType == 212 && position.X >= 24,
                 EExtraSkipCondition.RisingStonesSolar => territoryType == 351 && position.Z <= -28,
                 EExtraSkipCondition.RoguesGuild => territoryType == 129 && position.Y <= -115,
+                EExtraSkipCondition.NotRoguesGuild => territoryType == 129 && position.Y > -115,
                 EExtraSkipCondition.DockStorehouse => territoryType == 137 && position.Y <= -20,
                 _ => throw new ArgumentOutOfRangeException(nameof(condition), condition, null)
             };
index d96dce2..0d0f2a5 100644 (file)
@@ -30,6 +30,11 @@ internal interface IStoppableTaskExecutor : ITaskExecutor
     void StopNow();
 }
 
+internal interface IDebugStateProvider : ITaskExecutor
+{
+    string? GetDebugState();
+}
+
 internal abstract class TaskExecutor<T> : ITaskExecutor
     where T : class, ITask
 {
index 0c8ce0a..ab43a5e 100644 (file)
@@ -20,6 +20,7 @@ internal sealed class NavmeshIpc
     private readonly ICallGateSubscriber<List<Vector3>> _pathListWaypoints;
     private readonly ICallGateSubscriber<float, object> _pathSetTolerance;
     private readonly ICallGateSubscriber<Vector3, bool, float, Vector3?> _queryPointOnFloor;
+    private readonly ICallGateSubscriber<float> _buildProgress;
 
     public NavmeshIpc(IDalamudPluginInterface pluginInterface, ILogger<NavmeshIpc> logger)
     {
@@ -35,6 +36,7 @@ internal sealed class NavmeshIpc
         _pathSetTolerance = pluginInterface.GetIpcSubscriber<float, object>("vnavmesh.Path.SetTolerance");
         _queryPointOnFloor =
             pluginInterface.GetIpcSubscriber<Vector3, bool, float, Vector3?>("vnavmesh.Query.Mesh.PointOnFloor");
+        _buildProgress = pluginInterface.GetIpcSubscriber<float>("vnavmesh.Nav.BuildProgress");
     }
 
     public bool IsReady
@@ -136,4 +138,19 @@ internal sealed class NavmeshIpc
         else
             return [];
     }
+
+    public int GetBuildProgress()
+    {
+        try
+        {
+            float progress = _buildProgress.InvokeFunc();
+            if (progress < 0)
+                return 100;
+            return (int)(progress * 100);
+        }
+        catch (IpcError)
+        {
+            return 0;
+        }
+    }
 }
index 04d69bc..865718c 100644 (file)
@@ -235,6 +235,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
         serviceCollection.AddTaskExecutor<SinglePlayerDuty.SetTarget, SinglePlayerDuty.SetTargetExecutor>();
 
         serviceCollection.AddTaskExecutor<WaitCondition.Task, WaitCondition.WaitConditionExecutor>();
+        serviceCollection.AddTaskExecutor<WaitNavmesh.Task, WaitNavmesh.Executor>();
         serviceCollection.AddTaskFactory<WaitAtEnd.Factory>();
         serviceCollection.AddTaskExecutor<WaitAtEnd.WaitDelay, WaitAtEnd.WaitDelayExecutor>();
         serviceCollection.AddTaskExecutor<WaitAtEnd.WaitNextStepOrSequence, WaitAtEnd.WaitNextStepOrSequenceExecutor>();
index 7ec46f3..7452a4d 100644 (file)
@@ -73,25 +73,34 @@ internal sealed partial class ActiveQuestComponent
 
             if (_combatController.IsRunning)
                 ImGui.TextColored(ImGuiColors.DalamudOrange, "In Combat");
+            else if (_questController.CurrentTaskState is { } currentTaskState)
+            {
+                using var _ = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudOrange);
+                ImGui.TextUnformatted(currentTaskState);
+            }
             else
             {
-                ImGui.BeginDisabled();
+                using var _ = ImRaii.Disabled();
                 ImGui.TextUnformatted(_questController.DebugState ?? string.Empty);
-                ImGui.EndDisabled();
             }
 
             QuestSequence? currentSequence = currentQuest.Quest.FindSequence(currentQuest.Sequence);
             QuestStep? currentStep = currentSequence?.FindStep(currentQuest.Step);
             if (!isMinimized)
             {
-                bool colored = currentStep is
-                    { InteractionType: EInteractionType.Instruction or EInteractionType.WaitForManualProgress or EInteractionType.Snipe };
-                if (colored)
-                    ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudOrange);
-                ImGui.TextUnformatted(currentStep?.Comment ??
-                                      currentSequence?.Comment ?? currentQuest.Quest.Root.Comment ?? string.Empty);
-                if (colored)
-                    ImGui.PopStyleColor();
+                using (var color = new ImRaii.Color())
+                {
+                    bool colored = currentStep is
+                    {
+                        InteractionType: EInteractionType.Instruction or EInteractionType.WaitForManualProgress
+                        or EInteractionType.Snipe
+                    };
+                    if (colored)
+                        color.Push(ImGuiCol.Text, ImGuiColors.DalamudOrange);
+
+                    ImGui.TextUnformatted(currentStep?.Comment ??
+                                          currentSequence?.Comment ?? currentQuest.Quest.Root.Comment ?? string.Empty);
+                }
 
                 //var nextStep = _questController.GetNextStep();
                 //ImGui.BeginDisabled(nextStep.Step == null);