Various fixes
authorLiza Carvelli <liza@carvel.li>
Mon, 10 Jun 2024 17:56:13 +0000 (19:56 +0200)
committerLiza Carvelli <liza@carvel.li>
Mon, 10 Jun 2024 17:56:13 +0000 (19:56 +0200)
13 files changed:
QuestPaths/Endwalker/MSQ/B-Garlemald/4387_A Way Forward.json
QuestPaths/Endwalker/MSQ/B-Garlemald/4389_Personae non Gratae.json
QuestPaths/Endwalker/MSQ/B-Garlemald/4390_His Park Materials.json
QuestPaths/Endwalker/MSQ/F-Labyrinthos2/4446_Hither and Yarns.json
Questionable/Controller/QuestController.cs
Questionable/Controller/Steps/BaseFactory/SkipCondition.cs
Questionable/Controller/Steps/BaseFactory/WaitAtEnd.cs
Questionable/Controller/Steps/BaseTasks/MountTask.cs
Questionable/Controller/Steps/InteractionFactory/Combat.cs
Questionable/Controller/Steps/InteractionFactory/Interact.cs
Questionable/DalamudInitializer.cs
Questionable/GameFunctions.cs
Questionable/Windows/DebugWindow.cs

index 6e96c3c5542ce5f43ecae9f852f7aa50e708b566..49796fd656c0999e862c179da80428c630f96b0b 100644 (file)
@@ -77,7 +77,7 @@
             "Z": 365.7129
           },
           "TerritoryId": 958,
-          "InteractionType": "WaitForManualProgress",
+          "InteractionType": "SinglePlayerDuty",
           "Comment": "Follow Alphinaud and Alisaie",
           "DialogueChoices": [
             {
index 81827ee08ed5c3c5ca100403034330aacf527519..fb628bc45de98e0a49ab9a86aa06f92af7243561 100644 (file)
@@ -76,7 +76,7 @@
             null,
             null,
             null,
-            128
+            -128
           ]
         },
         {
index cee2e95c05baefcc14fc2a6877b5678b2d5b1894..fd0028ed5c3a8e888ccb5c97ddc53e9ef0d691b1 100644 (file)
             "Z": -503.2578
           },
           "TerritoryId": 958,
-          "InteractionType": "Interact"
+          "InteractionType": "Interact",
+          "CompletionQuestVariablesFlags": [
+            null,
+            null,
+            null,
+            null,
+            null,
+            128
+          ]
         },
         {
           "DataId": 2012094,
           "TerritoryId": 958,
           "InteractionType": "Interact",
           "Comment": "Map",
-          "Mount": true
+          "Mount": true,
+          "CompletionQuestVariablesFlags": [
+            null,
+            null,
+            null,
+            null,
+            null,
+            64
+          ]
         },
         {
           "DataId": 2012005,
           },
           "TerritoryId": 958,
           "InteractionType": "Interact",
-          "Comment": "Warmachine Wreckage"
+          "Comment": "Warmachine Wreckage",
+          "CompletionQuestVariablesFlags": [
+            null,
+            null,
+            null,
+            null,
+            null,
+            32
+          ]
         },
         {
           "DataId": 2012096,
           },
           "TerritoryId": 958,
           "InteractionType": "Interact",
-          "Comment": "Children's Slide"
+          "Comment": "Children's Slide",
+          "CompletionQuestVariablesFlags": [
+            null,
+            null,
+            null,
+            null,
+            null,
+            16
+          ]
         }
       ]
     },
index d954b745a6ce6821b8c0915fe61c234daefde764..b00a949d44085901365412ff50cc7d9128d914b2 100644 (file)
     {
       "Sequence": 1,
       "Steps": [
+        {
+          "Position": {
+            "X": -148.48793,
+            "Y": -10.30035,
+            "Z": -247.25652
+          },
+          "TerritoryId": 956,
+          "InteractionType": "WalkTo",
+          "Comment": "Avoids Combat"
+        },
         {
           "DataId": 2011987,
           "Position": {
           "Mount": true,
           "DisableNavmesh": true
         },
+        {
+          "Position": {
+            "X": -480.30975,
+            "Y": -22.946651,
+            "Z": -145.08534
+          },
+          "TerritoryId": 956,
+          "InteractionType": "WalkTo",
+          "Comment": "Avoids Combat (typically)"
+        },
         {
           "DataId": 2011988,
           "Position": {
index 14b076ce10902752e0c54bc8dbbb748c69005689..a47aee57e2885ed6cf27dfc3f502f68a3bfa6ecb 100644 (file)
@@ -71,7 +71,8 @@ internal sealed class QuestController
 
         if (_keyState[VirtualKey.ESCAPE])
         {
-            Stop();
+            if (_currentTask != null || _taskQueue.Count > 0)
+                Stop("ESC pressed");
             _movementController.Stop();
         }
 
@@ -92,7 +93,7 @@ internal sealed class QuestController
             {
                 _logger.LogInformation("No current quest, resetting data");
                 CurrentQuest = null;
-                Stop();
+                Stop("Resetting current quest");
             }
         }
         else if (CurrentQuest == null || CurrentQuest.Quest.QuestId != currentQuestId)
@@ -101,14 +102,15 @@ internal sealed class QuestController
             {
                 _logger.LogInformation("New quest: {QuestName}", quest.Name);
                 CurrentQuest = new QuestProgress(quest, currentSequence, 0);
+                Stop("Different Quest");
             }
             else if (CurrentQuest != null)
             {
                 _logger.LogInformation("No active quest anymore? Not sure what happened...");
                 CurrentQuest = null;
+                Stop("No active Quest");
             }
 
-            Stop();
             return;
         }
 
@@ -116,7 +118,7 @@ internal sealed class QuestController
         {
             DebugState = "No quest active";
             Comment = null;
-            Stop();
+            Stop("No quest active");
             return;
         }
 
@@ -140,11 +142,7 @@ internal sealed class QuestController
         if (CurrentQuest.Sequence != currentSequence)
         {
             CurrentQuest = CurrentQuest with { Sequence = currentSequence, Step = 0 };
-
-            bool automatic = _automatic;
-            Stop();
-            if (automatic)
-                ExecuteNextStep(true);
+            Stop("New sequence", continueIfAutomatic: true);
         }
 
         var q = CurrentQuest.Quest;
@@ -153,7 +151,7 @@ internal sealed class QuestController
         {
             DebugState = "Sequence not found";
             Comment = null;
-            Stop();
+            Stop("Unknown sequence");
             return;
         }
 
@@ -161,7 +159,8 @@ internal sealed class QuestController
         {
             DebugState = "Step completed";
             Comment = null;
-            Stop();
+            if (_currentTask != null || _taskQueue.Count > 0)
+                Stop("Step complete", continueIfAutomatic: true);
             return;
         }
 
@@ -169,7 +168,7 @@ internal sealed class QuestController
         {
             DebugState = "Step not found";
             Comment = null;
-            Stop();
+            Stop("Unknown step");
             return;
         }
 
@@ -249,15 +248,31 @@ internal sealed class QuestController
             */
     }
 
-    public void Stop()
+    private void ClearTasksInternal()
     {
         _currentTask = null;
 
         if (_taskQueue.Count > 0)
             _taskQueue.Clear();
+    }
+
+    public void Stop(string label, bool continueIfAutomatic = false)
+    {
+        using var scope = _logger.BeginScope(label);
+
+        ClearTasksInternal();
 
         // reset task queue
-        _automatic = false;
+        if (continueIfAutomatic && _automatic)
+        {
+            if (CurrentQuest?.Step is >= 0 and < 255)
+                ExecuteNextStep(true);
+        }
+        else
+        {
+            _logger.LogInformation("Stopping automatic questing");
+            _automatic = false;
+        }
     }
 
     private void UpdateCurrentTask()
@@ -286,7 +301,7 @@ internal sealed class QuestController
                 catch (Exception e)
                 {
                     _logger.LogError(e, "Failed to start task {TaskName}", upcomingTask.ToString());
-                    Stop();
+                    Stop("Task failed to start");
                     return;
                 }
             }
@@ -302,7 +317,7 @@ internal sealed class QuestController
         catch (Exception e)
         {
             _logger.LogError(e, "Failed to update task {TaskName}", _currentTask.ToString());
-            Stop();
+            Stop("Task failed to update");
             return;
         }
 
@@ -312,7 +327,8 @@ internal sealed class QuestController
                 return;
 
             case ETaskResult.SkipRemainingTasksForStep:
-                _logger.LogInformation("Result: {Result}, skipping remaining tasks for step", result);
+                _logger.LogInformation("{Task} → {Result}, skipping remaining tasks for step",
+                    _currentTask, result);
                 _currentTask = null;
 
                 while (_taskQueue.TryDequeue(out ITask? nextTask))
@@ -327,28 +343,28 @@ internal sealed class QuestController
                 return;
 
             case ETaskResult.TaskComplete:
-                _logger.LogInformation("Result: {Result}, remaining tasks: {RemainingTaskCount}", result,
-                    _taskQueue.Count);
+                _logger.LogInformation("{Task} → {Result}, remaining tasks: {RemainingTaskCount}",
+                    _currentTask, result, _taskQueue.Count);
                 _currentTask = null;
 
                 // handled in next update
                 return;
 
             case ETaskResult.NextStep:
-                _logger.LogInformation("Result: {Result}", result);
+                _logger.LogInformation("{Task} → {Result}", _currentTask, result);
                 IncreaseStepCount(true);
                 return;
 
             case ETaskResult.End:
-                _logger.LogInformation("Result: {Result}", result);
-                Stop();
+                _logger.LogInformation("{Task} → {Result}", _currentTask, result);
+                Stop("Task end");
                 return;
         }
     }
 
     public void ExecuteNextStep(bool automatic)
     {
-        Stop();
+        ClearTasksInternal();
         _automatic = automatic;
 
         (QuestSequence? seq, QuestStep? step) = GetNextStep();
@@ -382,6 +398,9 @@ internal sealed class QuestController
             return;
         }
 
+        _logger.LogInformation("Tasks for {QuestId}, {Sequence}, {Step}: {Tasks}",
+            CurrentQuest.Quest.QuestId, seq.Sequence, seq.Steps.IndexOf(step),
+            string.Join(", ", newTasks.Select(x => x.ToString())));
         foreach (var task in newTasks)
             _taskQueue.Enqueue(task);
     }
@@ -394,6 +413,9 @@ internal sealed class QuestController
         return _currentTask == null ? $"- (+{_taskQueue.Count})" : $"{_currentTask} (+{_taskQueue.Count})";
     }
 
+    public bool HasCurrentTaskMatching<T>() =>
+        _currentTask is T;
+
     public sealed record QuestProgress(
         Quest Quest,
         byte Sequence,
index 6d288d5fc0e4d527244a7dfda17ee14ecd585f32..f025a622ca5ba8019a08a9b81700dd7c7395f4df 100644 (file)
@@ -14,10 +14,10 @@ internal static class SkipCondition
     {
         public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
-            if (step.SkipIf.Count == 0)
+            if (step.SkipIf.Contains(ESkipCondition.Never))
                 return null;
 
-            if (step.SkipIf.Contains(ESkipCondition.Never))
+            if (step.SkipIf.Count == 0 && step.CompletionQuestVariablesFlags.Count == 0)
                 return null;
 
             return serviceProvider.GetRequiredService<CheckTask>()
index e0519faf60d2c8398b5fac92c17423f61febe41d..91a43e262db12e2493d1468cf9ed2fd4dc0a0ad3 100644 (file)
@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
 using System.Numerics;
+using Dalamud.Plugin.Services;
 using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
 using Microsoft.Extensions.DependencyInjection;
 using Questionable.Controller.Steps.BaseTasks;
@@ -13,7 +14,7 @@ namespace Questionable.Controller.Steps.BaseFactory;
 
 internal static class WaitAtEnd
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class Factory(IServiceProvider serviceProvider, IClientState clientState) : ITaskFactory
     {
         public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
         {
@@ -54,6 +55,36 @@ internal static class WaitAtEnd
                         new NextStep()
                     ];
 
+                case EInteractionType.Interact when step.TargetTerritoryId != null:
+                    ITask waitInteraction;
+                    if (step.TerritoryId != step.TargetTerritoryId)
+                    {
+                        // interaction moves to a different territory
+                        waitInteraction = new WaitConditionTask(() => clientState.TerritoryType == step.TerritoryId,
+                            $"Wait(tp to territory: {step.TerritoryId})");
+                    }
+                    else
+                    {
+                        Vector3 lastPosition = step.Position ?? clientState.LocalPlayer?.Position ?? Vector3.Zero;
+                        waitInteraction = new WaitConditionTask(() =>
+                            {
+                                Vector3? currentPosition = clientState.LocalPlayer?.Position;
+                                if (currentPosition == null)
+                                    return false;
+
+                                // interaction moved to elsewhere in the zone
+                                return (lastPosition - currentPosition.Value).Length() > 20;
+                            }, $"Wait(tp away from {lastPosition.ToString("G", CultureInfo.InvariantCulture)})");
+                    }
+
+                    return
+                    [
+                        waitInteraction,
+                        serviceProvider.GetRequiredService<WaitDelay>(),
+                        new NextStep()
+                    ];
+
+                case EInteractionType.Interact:
                 default:
                     return [serviceProvider.GetRequiredService<WaitDelay>(), new NextStep()];
             }
@@ -136,7 +167,7 @@ internal static class WaitAtEnd
 
         public ETaskResult Update() => ETaskResult.NextStep;
 
-        public override string ToString() => "Next Step";
+        public override string ToString() => "NextStep";
     }
 
     internal sealed class EndAutomation : ILastTask
@@ -145,6 +176,6 @@ internal static class WaitAtEnd
 
         public ETaskResult Update() => ETaskResult.End;
 
-        public override string ToString() => "End automation";
+        public override string ToString() => "EndAutomation";
     }
 }
index 1893804fdbe2f361b60003f78162575001622bb7..65fb649d9952c2004b704b4604e5cb2e7ec37d46 100644 (file)
@@ -32,7 +32,10 @@ internal sealed class MountTask(
         }
 
         if (gameFunctions.HasStatusPreventingSprintOrMount())
+        {
+            logger.LogInformation("Can't mount due to status preventing sprint or mount");
             return false;
+        }
 
         logger.LogInformation("Step wants a mount, trying to mount in territory {Id}...", _territoryId);
         if (!condition[ConditionFlag.InCombat])
@@ -48,6 +51,12 @@ internal sealed class MountTask(
     {
         if (!_mountTriggered)
         {
+            if (gameFunctions.HasStatusPreventingSprintOrMount())
+            {
+                logger.LogInformation("Can't mount due to status preventing sprint or mount");
+                return ETaskResult.TaskComplete;
+            }
+
             _mountTriggered = gameFunctions.Mount();
             return ETaskResult.StillRunning;
         }
index 969b8055d1642d86501e11d8d1e19d6c98b845a3..b9ebcc7515f085691cb05b00337a710fd9aa17d8 100644 (file)
@@ -24,7 +24,7 @@ internal static class Combat
                 ArgumentNullException.ThrowIfNull(step.DataId);
 
                 var task = serviceProvider.GetRequiredService<Interact.DoInteract>()
-                    .With(step.DataId.Value);
+                    .With(step.DataId.Value, true);
                 return [unmount, task];
             }
             else if (step.EnemySpawnType == EEnemySpawnType.AfterItemUse)
index fb7db657d5e3743f85fe7ff48b52496e1b659d17..8ab55778ec5d3643db8cd40ee1c718a24c4fb7f3 100644 (file)
@@ -21,21 +21,25 @@ internal static class Interact
 
             ArgumentNullException.ThrowIfNull(step.DataId);
 
-            return serviceProvider.GetRequiredService<DoInteract>().With(step.DataId.Value);
+            return serviceProvider.GetRequiredService<DoInteract>()
+                .With(step.DataId.Value, step.TargetTerritoryId != null);
         }
     }
 
     internal sealed class DoInteract(GameFunctions gameFunctions, ICondition condition, ILogger<DoInteract> logger)
         : ITask
     {
+        private bool _needsUnmount;
         private bool _interacted;
         private DateTime _continueAt = DateTime.MinValue;
 
         private uint DataId { get; set; }
+        private bool SkipMarkerCheck { get; set; }
 
-        public ITask With(uint dataId)
+        public ITask With(uint dataId, bool skipMarkerCheck)
         {
             DataId = dataId;
+            SkipMarkerCheck = skipMarkerCheck;
             return this;
         }
 
@@ -51,6 +55,7 @@ internal static class Interact
             // this is only relevant for followers on quests
             if (!gameObject.IsTargetable && condition[ConditionFlag.Mounted])
             {
+                _needsUnmount = true;
                 gameFunctions.Unmount();
                 _continueAt = DateTime.Now.AddSeconds(0.5);
                 return true;
@@ -71,6 +76,18 @@ internal static class Interact
             if (DateTime.Now <= _continueAt)
                 return ETaskResult.StillRunning;
 
+            if (_needsUnmount)
+            {
+                if (condition[ConditionFlag.Mounted])
+                {
+                    gameFunctions.Unmount();
+                    _continueAt = DateTime.Now.AddSeconds(0.5);
+                    return ETaskResult.StillRunning;
+                }
+                else
+                    _needsUnmount = false;
+            }
+
             if (!_interacted)
             {
                 GameObject? gameObject = gameFunctions.FindObjectByDataId(DataId);
@@ -87,7 +104,7 @@ internal static class Interact
 
         private unsafe bool HasAnyMarker(GameObject gameObject)
         {
-            if (gameObject.ObjectKind != ObjectKind.EventNpc)
+            if (SkipMarkerCheck || gameObject.ObjectKind != ObjectKind.EventNpc)
                 return true;
 
             var gameObjectStruct = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gameObject.Address;
index e2d03cf7f8d650033286e45b48f8159709412e22..44607f2d228b81df7347532143d3ae7feee40732 100644 (file)
@@ -54,7 +54,7 @@ internal sealed class DalamudInitializer : IDisposable
         }
         catch (MovementController.PathfindingFailedException)
         {
-            _questController.Stop();
+            _questController.Stop("Pathfinding failed");
         }
     }
 
index c868f286b3ec16a5328af8dedd9cfec90f315a64..f9bb1183f5b0fff718a06e2f15f931f6f7c11fc8 100644 (file)
@@ -470,17 +470,18 @@ internal sealed unsafe class GameFunctions
     public bool Unmount()
     {
         if (!_condition[ConditionFlag.Mounted])
-            return false;
+            return true;
 
         if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 23) == 0)
         {
             _logger.LogInformation("Unmounting...");
-            ActionManager.Instance()->UseAction(ActionType.GeneralAction, 23);
+            return ActionManager.Instance()->UseAction(ActionType.GeneralAction, 23);
         }
         else
+        {
             _logger.LogWarning("Can't unmount right now?");
-
-        return true;
+            return false;
+        }
     }
 
     public void OpenDutyFinder(uint contentFinderConditionId)
index 2d9b7837f6c74a0becde89265c0b4791b950d77b..7e2d353e3ad226cd16f37843cf7e2a1136d60c3a 100644 (file)
@@ -4,6 +4,7 @@ using System.Globalization;
 using System.Numerics;
 using Dalamud.Game.ClientState.Objects;
 using Dalamud.Interface;
+using Dalamud.Interface.Colors;
 using Dalamud.Interface.Components;
 using Dalamud.Interface.Windowing;
 using Dalamud.Plugin;
@@ -16,6 +17,7 @@ using ImGuiNET;
 using LLib.ImGui;
 using Microsoft.Extensions.Logging;
 using Questionable.Controller;
+using Questionable.Controller.Steps.BaseFactory;
 using Questionable.Model;
 using Questionable.Model.V1;
 
@@ -85,6 +87,7 @@ internal sealed class DebugWindow : LWindow, IPersistableWindowConfig, IDisposab
         ImGui.Separator();
 
         DrawQuickAccessButtons();
+        DrawRemainingTasks();
     }
 
     private unsafe void DrawQuest()
@@ -141,14 +144,25 @@ internal sealed class DebugWindow : LWindow, IPersistableWindowConfig, IDisposab
             if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop))
             {
                 _movementController.Stop();
-                _questController.Stop();
+                _questController.Stop("Manual");
             }
 
+            QuestStep? currentStep = currentQuest.Quest
+                .FindSequence(currentQuest.Sequence)
+                ?.FindStep(currentQuest.Step);
+            bool colored = currentStep != null && currentStep.InteractionType == EInteractionType.Instruction
+                && _questController.HasCurrentTaskMatching<WaitAtEnd.WaitNextStepOrSequence>();
+
+            if (colored)
+                ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen);
             if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.ArrowCircleRight, "Skip"))
             {
-                _questController.Stop();
+                _movementController.Stop();
+                _questController.Stop("Manual");
                 _questController.IncreaseStepCount();
             }
+            if (colored)
+                ImGui.PopStyleColor();
         }
         else
             ImGui.Text("No active quest");
@@ -278,7 +292,7 @@ internal sealed class DebugWindow : LWindow, IPersistableWindowConfig, IDisposab
         if (ImGui.Button("Stop Nav"))
         {
             _movementController.Stop();
-            _questController.Stop();
+            _questController.Stop("Manual");
         }
 
         ImGui.EndDisabled();
@@ -289,7 +303,10 @@ internal sealed class DebugWindow : LWindow, IPersistableWindowConfig, IDisposab
             _framework.RunOnTick(() => _gameUiController.HandleCurrentDialogueChoices(),
                 TimeSpan.FromMilliseconds(200));
         }
+    }
 
+    private void DrawRemainingTasks()
+    {
         var remainingTasks = _questController.GetRemainingTaskNames();
         if (remainingTasks.Count > 0)
         {