Fix gathering for tribal quests
authorLiza Carvelli <liza@carvel.li>
Mon, 18 Nov 2024 18:05:22 +0000 (19:05 +0100)
committerLiza Carvelli <liza@carvel.li>
Mon, 18 Nov 2024 18:05:22 +0000 (19:05 +0100)
Questionable/Controller/MiniTaskController.cs
Questionable/Controller/Steps/ETaskResult.cs
Questionable/Controller/Steps/Shared/Gather.cs
Questionable/Controller/Steps/TaskExecutor.cs
Questionable/Controller/Steps/TaskQueue.cs
Questionable/QuestionablePlugin.cs

index 0685081809b8ceefee8d23041e4086a8aef1464e..06e5d874d983afd014090667e19417c32757e1a8 100644 (file)
@@ -120,11 +120,15 @@ internal abstract class MiniTaskController<T>
                 return;
 
             case ETaskResult.TaskComplete:
+            case ETaskResult.CreateNewTasks:
                 _logger.LogInformation("{Task} → {Result}, remaining tasks: {RemainingTaskCount}",
                     _taskQueue.CurrentTaskExecutor.CurrentTask, result, _taskQueue.RemainingTasks.Count());
 
                 OnTaskComplete(_taskQueue.CurrentTaskExecutor.CurrentTask);
 
+                if (result == ETaskResult.CreateNewTasks && _taskQueue.CurrentTaskExecutor is IExtraTaskCreator extraTaskCreator)
+                    _taskQueue.EnqueueAll(extraTaskCreator.CreateExtraTasks());
+
                 _taskQueue.CurrentTaskExecutor = null;
 
                 // handled in next update
index 647e6ae18f93b544c2e0850e0f379ef2a8995a7d..6ecfcba642977f28d135c9792f781ce2b25ff985 100644 (file)
@@ -11,6 +11,11 @@ internal enum ETaskResult
     /// </summary>
     SkipRemainingTasksForStep,
 
+    /// <summary>
+    /// Assumes the task executor implements <see cref="IExtraTaskCreator"/>.
+    /// </summary>
+    CreateNewTasks,
+
     NextStep,
     End,
 }
index 13671d8d9a14f8aa109cefa69b8ae61ce1407b38..73dd8d12f4c80884666f47b06ccf358e8b5cafa9 100644 (file)
@@ -4,7 +4,6 @@ using System.Linq;
 using Dalamud.Game.Text;
 using Dalamud.Game.Text.SeStringHandling;
 using Dalamud.Plugin.Services;
-using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
 using FFXIVClientStructs.FFXIV.Client.Game;
 using LLib.GameData;
 using Microsoft.Extensions.DependencyInjection;
@@ -19,14 +18,7 @@ namespace Questionable.Controller.Steps.Shared;
 
 internal static class Gather
 {
-    internal sealed class Factory(
-        IServiceProvider serviceProvider,
-        MovementController movementController,
-        GatheringPointRegistry gatheringPointRegistry,
-        IClientState clientState,
-        GatheringData gatheringData,
-        TerritoryData territoryData,
-        ILogger<Factory> logger) : ITaskFactory
+    internal sealed class Factory : ITaskFactory
     {
         public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
         {
@@ -35,45 +27,69 @@ internal static class Gather
 
             foreach (var itemToGather in step.ItemsToGather)
             {
-                EClassJob currentClassJob = (EClassJob)clientState.LocalPlayer!.ClassJob.RowId;
-                if (!gatheringData.TryGetGatheringPointId(itemToGather.ItemId, currentClassJob,
-                        out GatheringPointId? gatheringPointId))
-                    throw new TaskException($"No gathering point found for item {itemToGather.ItemId}");
+                yield return new DelayedGatheringTask(itemToGather, quest);
+            }
+        }
+    }
 
-                if (!gatheringPointRegistry.TryGetGatheringPoint(gatheringPointId, out GatheringRoot? gatheringRoot))
-                    throw new TaskException($"No path found for gathering point {gatheringPointId}");
+    internal sealed record DelayedGatheringTask(GatheredItem GatheredItem, Quest Quest) : ITask
+    {
+        public override string ToString() => $"Gathering(pending for {GatheredItem.ItemId})";
+    }
 
-                if (HasRequiredItems(itemToGather))
-                    continue;
+    internal sealed class DelayedGatheringExecutor(
+        MovementController movementController,
+        GatheringData gatheringData,
+        GatheringPointRegistry gatheringPointRegistry,
+        TerritoryData territoryData,
+        IClientState clientState,
+        IServiceProvider serviceProvider,
+        ILogger<DelayedGatheringExecutor> logger) : TaskExecutor<DelayedGatheringTask>, IExtraTaskCreator
+    {
+        protected override bool Start() => true;
+
+        public override ETaskResult Update() => ETaskResult.CreateNewTasks;
+
+        public IEnumerable<ITask> CreateExtraTasks()
+        {
+            EClassJob currentClassJob = (EClassJob)clientState.LocalPlayer!.ClassJob.RowId;
+            if (!gatheringData.TryGetGatheringPointId(Task.GatheredItem.ItemId, currentClassJob,
+                    out GatheringPointId? gatheringPointId))
+                throw new TaskException($"No gathering point found for item {Task.GatheredItem.ItemId}");
 
-                using (var _ = logger.BeginScope("Gathering(inner)"))
+            if (!gatheringPointRegistry.TryGetGatheringPoint(gatheringPointId, out GatheringRoot? gatheringRoot))
+                throw new TaskException($"No path found for gathering point {gatheringPointId}");
+
+            if (HasRequiredItems(Task.GatheredItem))
+                yield break;
+
+            using (var _ = logger.BeginScope("Gathering(inner)"))
+            {
+                QuestSequence gatheringSequence = new QuestSequence
                 {
-                    QuestSequence gatheringSequence = new QuestSequence
-                    {
-                        Sequence = 0,
-                        Steps = gatheringRoot.Steps
-                    };
-                    foreach (var gatheringStep in gatheringSequence.Steps)
-                    {
-                        foreach (var task in serviceProvider.GetRequiredService<TaskCreator>()
-                                     .CreateTasks(quest, gatheringSequence, gatheringStep))
-                            if (task is WaitAtEnd.NextStep)
-                                yield return new SkipMarker();
-                            else
-                                yield return task;
-                    }
+                    Sequence = 0,
+                    Steps = gatheringRoot.Steps
+                };
+                foreach (var gatheringStep in gatheringSequence.Steps)
+                {
+                    foreach (var task in serviceProvider.GetRequiredService<TaskCreator>()
+                                 .CreateTasks(Task.Quest, gatheringSequence, gatheringStep))
+                        if (task is WaitAtEnd.NextStep)
+                            yield return new SkipMarker();
+                        else
+                            yield return task;
                 }
+            }
 
-                ushort territoryId = gatheringRoot.Steps.Last().TerritoryId;
-                yield return new WaitCondition.Task(() => clientState.TerritoryType == territoryId,
-                    $"Wait(territory: {territoryData.GetNameAndId(territoryId)})");
+            ushort territoryId = gatheringRoot.Steps.Last().TerritoryId;
+            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 WaitCondition.Task(() => movementController.IsNavmeshReady,
+                "Wait(navmesh ready)");
 
-                yield return new GatheringTask(gatheringPointId, itemToGather);
-                yield return new WaitAtEnd.WaitDelay();
-            }
+            yield return new GatheringTask(gatheringPointId, Task.GatheredItem);
+            yield return new WaitAtEnd.WaitDelay();
         }
 
         private unsafe bool HasRequiredItems(GatheredItem itemToGather)
index 9f1873f60d592158aea7531dd4b8ab8a313fa4fa..d0315dbcd1156bbe8c83116729a5df0c02387936 100644 (file)
@@ -1,4 +1,6 @@
 using System;
+using System.Collections.Generic;
+using Questionable.Model;
 
 namespace Questionable.Controller.Steps;
 
@@ -16,6 +18,11 @@ internal interface ITaskExecutor
     ETaskResult Update();
 }
 
+internal interface IExtraTaskCreator : ITaskExecutor
+{
+    IEnumerable<ITask> CreateExtraTasks();
+}
+
 internal abstract class TaskExecutor<T> : ITaskExecutor
     where T : class, ITask
 {
index 7d30706ac41985062f2e18eab9d96954c18dc36a..04cb273ccf62682fe708bca17c1b02653fa7057f 100644 (file)
@@ -18,6 +18,11 @@ internal sealed class TaskQueue
         _tasks.Add(task);
     }
 
+    public void EnqueueAll(IEnumerable<ITask> tasks)
+    {
+        _tasks.InsertRange(0, tasks);
+    }
+
     public bool TryDequeue([NotNullWhen(true)] out ITask? task)
     {
         task = _tasks.FirstOrDefault();
index ea0eefca40f5b98b742bbb49e63190d57c331e3e..7e9fb3f53bd5775e97bf140d00747a522b7d8023 100644 (file)
@@ -147,7 +147,6 @@ public sealed class QuestionablePlugin : IDalamudPlugin
             .AddTaskFactoryAndExecutor<StepDisabled.SkipRemainingTasks, StepDisabled.Factory,
                 StepDisabled.SkipDisabledStepsExecutor>();
         serviceCollection.AddTaskFactory<EquipRecommended.BeforeDutyOrInstance>();
-        serviceCollection.AddTaskFactoryAndExecutor<Gather.GatheringTask, Gather.Factory, Gather.StartGathering>();
         serviceCollection.AddTaskExecutor<Gather.SkipMarker, Gather.DoSkip>();
         serviceCollection
             .AddTaskFactoryAndExecutor<AetheryteShortcut.Task, AetheryteShortcut.Factory,
@@ -156,6 +155,8 @@ public sealed class QuestionablePlugin : IDalamudPlugin
             .AddTaskExecutor<AetheryteShortcut.MoveAwayFromAetheryte, AetheryteShortcut.MoveAwayFromAetheryteExecutor>();
         serviceCollection
             .AddTaskFactoryAndExecutor<SkipCondition.SkipTask, SkipCondition.Factory, SkipCondition.CheckSkip>();
+        serviceCollection.AddTaskFactoryAndExecutor<Gather.GatheringTask, Gather.Factory, Gather.StartGathering>();
+        serviceCollection.AddTaskExecutor<Gather.DelayedGatheringTask, Gather.DelayedGatheringExecutor>();
         serviceCollection
             .AddTaskFactoryAndExecutor<AethernetShortcut.Task, AethernetShortcut.Factory,
                 AethernetShortcut.UseAethernetShortcut>();