diff --git a/Questionable/Controller/MiniTaskController.cs b/Questionable/Controller/MiniTaskController.cs index 068508180..06e5d874d 100644 --- a/Questionable/Controller/MiniTaskController.cs +++ b/Questionable/Controller/MiniTaskController.cs @@ -120,11 +120,15 @@ internal abstract class MiniTaskController 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 diff --git a/Questionable/Controller/Steps/ETaskResult.cs b/Questionable/Controller/Steps/ETaskResult.cs index 647e6ae18..6ecfcba64 100644 --- a/Questionable/Controller/Steps/ETaskResult.cs +++ b/Questionable/Controller/Steps/ETaskResult.cs @@ -11,6 +11,11 @@ internal enum ETaskResult /// SkipRemainingTasksForStep, + /// + /// Assumes the task executor implements . + /// + CreateNewTasks, + NextStep, End, } diff --git a/Questionable/Controller/Steps/Shared/Gather.cs b/Questionable/Controller/Steps/Shared/Gather.cs index 13671d8d9..73dd8d12f 100644 --- a/Questionable/Controller/Steps/Shared/Gather.cs +++ b/Questionable/Controller/Steps/Shared/Gather.cs @@ -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 logger) : ITaskFactory + internal sealed class Factory : ITaskFactory { public IEnumerable CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { @@ -35,46 +27,70 @@ 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}"); - - if (!gatheringPointRegistry.TryGetGatheringPoint(gatheringPointId, out GatheringRoot? gatheringRoot)) - throw new TaskException($"No path found for gathering point {gatheringPointId}"); - - if (HasRequiredItems(itemToGather)) - continue; - - using (var _ = logger.BeginScope("Gathering(inner)")) - { - QuestSequence gatheringSequence = new QuestSequence - { - Sequence = 0, - Steps = gatheringRoot.Steps - }; - foreach (var gatheringStep in gatheringSequence.Steps) - { - foreach (var task in serviceProvider.GetRequiredService() - .CreateTasks(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)})"); - - yield return new WaitCondition.Task(() => movementController.IsNavmeshReady, - "Wait(navmesh ready)"); - - yield return new GatheringTask(gatheringPointId, itemToGather); - yield return new WaitAtEnd.WaitDelay(); + yield return new DelayedGatheringTask(itemToGather, quest); } } + } + + internal sealed record DelayedGatheringTask(GatheredItem GatheredItem, Quest Quest) : ITask + { + public override string ToString() => $"Gathering(pending for {GatheredItem.ItemId})"; + } + + internal sealed class DelayedGatheringExecutor( + MovementController movementController, + GatheringData gatheringData, + GatheringPointRegistry gatheringPointRegistry, + TerritoryData territoryData, + IClientState clientState, + IServiceProvider serviceProvider, + ILogger logger) : TaskExecutor, IExtraTaskCreator + { + protected override bool Start() => true; + + public override ETaskResult Update() => ETaskResult.CreateNewTasks; + + public IEnumerable 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}"); + + 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 + { + Sequence = 0, + Steps = gatheringRoot.Steps + }; + foreach (var gatheringStep in gatheringSequence.Steps) + { + foreach (var task in serviceProvider.GetRequiredService() + .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)})"); + + yield return new WaitCondition.Task(() => movementController.IsNavmeshReady, + "Wait(navmesh ready)"); + + yield return new GatheringTask(gatheringPointId, Task.GatheredItem); + yield return new WaitAtEnd.WaitDelay(); + } private unsafe bool HasRequiredItems(GatheredItem itemToGather) { diff --git a/Questionable/Controller/Steps/TaskExecutor.cs b/Questionable/Controller/Steps/TaskExecutor.cs index 9f1873f60..d0315dbcd 100644 --- a/Questionable/Controller/Steps/TaskExecutor.cs +++ b/Questionable/Controller/Steps/TaskExecutor.cs @@ -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 CreateExtraTasks(); +} + internal abstract class TaskExecutor : ITaskExecutor where T : class, ITask { diff --git a/Questionable/Controller/Steps/TaskQueue.cs b/Questionable/Controller/Steps/TaskQueue.cs index 7d30706ac..04cb273cc 100644 --- a/Questionable/Controller/Steps/TaskQueue.cs +++ b/Questionable/Controller/Steps/TaskQueue.cs @@ -18,6 +18,11 @@ internal sealed class TaskQueue _tasks.Add(task); } + public void EnqueueAll(IEnumerable tasks) + { + _tasks.InsertRange(0, tasks); + } + public bool TryDequeue([NotNullWhen(true)] out ITask? task) { task = _tasks.FirstOrDefault(); diff --git a/Questionable/QuestionablePlugin.cs b/Questionable/QuestionablePlugin.cs index ea0eefca4..7e9fb3f53 100644 --- a/Questionable/QuestionablePlugin.cs +++ b/Questionable/QuestionablePlugin.cs @@ -147,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();