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 ede8e6c66058df5db767fb615a7f294b785ce245..595974c1be225cef62e956fa2249624bfc87ef3d 100644 (file)
                     "WakingSandsSolar",
                     "RisingStonesSolar",
                     "RoguesGuild",
+                    "NotRoguesGuild",
                     "DockStorehouse"
                   ]
                 }
index eb9179501501193e4a56c29bd2f178d160c19541..b49a1cfb417756eaa98e3245034757826520bff8 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 8ec77d4963af967e7648bc4adb626432e8c33ddc..47de211337fde12cb7bfe7eed76dd4b64829d9a6 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 ef6a1abd74b35d326dc7dd84f9b6ed2b342369e7..528fc571ef06e2ed58c2b5900d1d292cca2488b3 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 cc8012cf675d6734bd1ac94d5ed5af7f86d5f592..338bea94e41c77e1ac2d93cf3a0ddae924f7e98b 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 b509de18aad9075e726101644d8ab66957b1158f..c10d160c6ebc5a47fbbd6b8654fa3e4188c3a1ed 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 7bf43b810dda59139ea0a5538e23e9d11d1f5faf..ef61cb46140224fd45b445908a5f22645df15bee 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 460aa440b1dd694c46d3165cdce36ab75e62d7a8..ff1c585a61a5a99abae9ac3a757cc3f45afbd2f9 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 f4aad9c943b190297d8c3dfffa52ccaf8fb26585..45c007c429318f06e1d728e55af5c858f977aa2e 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 60d83aedd3ad2eee59a4eba88d1dbc8b05534aff..741c5433cd70a62c55311dd09dcdcbd36b297d22 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 bd9535397c701feb5d9526444aa45997b6938ed1..b651165fd0dc3f875ecc38d7d223515dc1827cea 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 d96dce2f9e2db009f590e395929fed4ddca97573..0d0f2a57667d53e933469a93857640cf71357c02 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 0c8ce0a7151c1a74c036357cd36e1bb8dc6010db..ab43a5ea0b0a16d35d3da932bb158cf02c9bd4a3 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 04d69bcf2d336dabddfa39fcfc4905241da5125b..865718cbb1a761fc984ae2aa433d7b196d7a6edb 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 7ec46f3dd87104bf1cc94de17be2dbb67d07a2b7..7452a4d71a6bad04805b685fceec6534998b739a 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);