Update how task factories work
authorLiza Carvelli <liza@carvel.li>
Tue, 20 Aug 2024 00:48:06 +0000 (02:48 +0200)
committerLiza Carvelli <liza@carvel.li>
Tue, 20 Aug 2024 00:48:06 +0000 (02:48 +0200)
39 files changed:
Questionable/Controller/GameUi/InteractionUiController.cs
Questionable/Controller/GatheringController.cs
Questionable/Controller/Steps/Common/Mount.cs [new file with mode: 0644]
Questionable/Controller/Steps/Common/MountTask.cs [deleted file]
Questionable/Controller/Steps/Common/NextQuest.cs
Questionable/Controller/Steps/Common/UnmountTask.cs [deleted file]
Questionable/Controller/Steps/Gathering/DoGather.cs
Questionable/Controller/Steps/Gathering/DoGatherCollectable.cs
Questionable/Controller/Steps/Gathering/MoveToLandingLocation.cs
Questionable/Controller/Steps/Gathering/TurnInDelivery.cs
Questionable/Controller/Steps/Interactions/Action.cs
Questionable/Controller/Steps/Interactions/AetherCurrent.cs
Questionable/Controller/Steps/Interactions/AethernetShard.cs
Questionable/Controller/Steps/Interactions/Aetheryte.cs
Questionable/Controller/Steps/Interactions/Combat.cs
Questionable/Controller/Steps/Interactions/Dive.cs
Questionable/Controller/Steps/Interactions/Duty.cs
Questionable/Controller/Steps/Interactions/Emote.cs
Questionable/Controller/Steps/Interactions/EquipItem.cs
Questionable/Controller/Steps/Interactions/EquipRecommended.cs
Questionable/Controller/Steps/Interactions/Interact.cs
Questionable/Controller/Steps/Interactions/Jump.cs
Questionable/Controller/Steps/Interactions/Say.cs
Questionable/Controller/Steps/Interactions/UseItem.cs
Questionable/Controller/Steps/Leves/InitiateLeve.cs
Questionable/Controller/Steps/Shared/AethernetShortcut.cs
Questionable/Controller/Steps/Shared/AetheryteShortcut.cs
Questionable/Controller/Steps/Shared/Craft.cs
Questionable/Controller/Steps/Shared/GatheringRequiredItems.cs
Questionable/Controller/Steps/Shared/Move.cs [deleted file]
Questionable/Controller/Steps/Shared/MoveTo.cs [new file with mode: 0644]
Questionable/Controller/Steps/Shared/SkipCondition.cs
Questionable/Controller/Steps/Shared/StepDisabled.cs
Questionable/Controller/Steps/Shared/SwitchClassJob.cs
Questionable/Controller/Steps/Shared/WaitAtEnd.cs
Questionable/Controller/Steps/Shared/WaitAtStart.cs
Questionable/Controller/Steps/TaskCreator.cs
Questionable/QuestionablePlugin.cs
Questionable/ServiceCollectionExtensions.cs

index 4c8367e72b9a0a422d781795404b45555de2cd7c..a6f1106083565c951a0ff491202fa999988837c3 100644 (file)
@@ -92,6 +92,14 @@ internal sealed class InteractionUiController : IDisposable
         _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "PointMenu", PointMenuPostSetup);
         _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "HousingSelectBlock", HousingSelectBlockPostSetup);
         _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "TelepotTown", TeleportTownPostSetup);
+
+        unsafe
+        {
+            if (_gameGui.TryGetAddonByName("RhythmAction", out AtkUnitBase* addon))
+            {
+                addon->Close(true);
+            }
+        }
     }
 
     private bool ShouldHandleUiInteractions => _isInitialCheck || _questController.IsRunning;
@@ -778,7 +786,7 @@ internal sealed class InteractionUiController : IDisposable
     {
         if (ShouldHandleUiInteractions &&
             _questController.HasCurrentTaskMatching(out AethernetShortcut.UseAethernetShortcut? aethernetShortcut) &&
-            EAetheryteLocationExtensions.IsFirmamentAetheryte(aethernetShortcut.From))
+            aethernetShortcut.From.IsFirmamentAetheryte())
         {
             // this might be better via atkvalues; but this works for now
             uint toIndex = aethernetShortcut.To switch
index e8ceccc3e7d80a5a132c07072554451e0eceb760..4dfae667274c51b05718f14d0748d962ab6ab94e 100644 (file)
@@ -25,24 +25,34 @@ using Questionable.Functions;
 using Questionable.GatheringPaths;
 using Questionable.Model.Gathering;
 using Questionable.Model.Questing;
+using Mount = Questionable.Controller.Steps.Common.Mount;
 
 namespace Questionable.Controller;
 
 internal sealed unsafe class GatheringController : MiniTaskController<GatheringController>
 {
     private readonly MovementController _movementController;
+    private readonly MoveTo.Factory _moveFactory;
+    private readonly Mount.Factory _mountFactory;
+    private readonly Interact.Factory _interactFactory;
     private readonly GatheringPointRegistry _gatheringPointRegistry;
     private readonly GameFunctions _gameFunctions;
     private readonly NavmeshIpc _navmeshIpc;
     private readonly IObjectTable _objectTable;
     private readonly IServiceProvider _serviceProvider;
     private readonly ICondition _condition;
+    private readonly ILoggerFactory _loggerFactory;
+    private readonly IGameGui _gameGui;
+    private readonly IClientState _clientState;
     private readonly Regex _revisitRegex;
 
     private CurrentRequest? _currentRequest;
 
     public GatheringController(
         MovementController movementController,
+        MoveTo.Factory moveFactory,
+        Mount.Factory mountFactory,
+        Interact.Factory interactFactory,
         GatheringPointRegistry gatheringPointRegistry,
         GameFunctions gameFunctions,
         NavmeshIpc navmeshIpc,
@@ -52,16 +62,25 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
         IServiceProvider serviceProvider,
         ICondition condition,
         IDataManager dataManager,
+        ILoggerFactory loggerFactory,
+        IGameGui gameGui,
+        IClientState clientState,
         IPluginLog pluginLog)
         : base(chatGui, logger)
     {
         _movementController = movementController;
+        _moveFactory = moveFactory;
+        _mountFactory = mountFactory;
+        _interactFactory = interactFactory;
         _gatheringPointRegistry = gatheringPointRegistry;
         _gameFunctions = gameFunctions;
         _navmeshIpc = navmeshIpc;
         _objectTable = objectTable;
         _serviceProvider = serviceProvider;
         _condition = condition;
+        _loggerFactory = loggerFactory;
+        _gameGui = gameGui;
+        _clientState = clientState;
 
         _revisitRegex = dataManager.GetRegex<LogMessage>(5574, x => x.Text, pluginLog)
                         ?? throw new InvalidDataException("No regex found for revisit message");
@@ -152,8 +171,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
             return;
 
         ushort territoryId = _currentRequest.Root.Steps.Last().TerritoryId;
-        _taskQueue.Enqueue(_serviceProvider.GetRequiredService<MountTask>()
-            .With(territoryId, MountTask.EMountIf.Always));
+        _taskQueue.Enqueue(_mountFactory.Mount(territoryId, Mount.EMountIf.Always));
 
         bool fly = currentNode.Fly.GetValueOrDefault(_currentRequest.Root.FlyBetweenNodes.GetValueOrDefault(true)) &&
                    _gameFunctions.IsFlyingUnlocked(territoryId);
@@ -170,24 +188,28 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
             if (pointOnFloor != null)
                 pointOnFloor = pointOnFloor.Value with { Y = pointOnFloor.Value.Y + (fly ? 3f : 0f) };
 
-            _taskQueue.Enqueue(_serviceProvider.GetRequiredService<Move.MoveInternal>()
-                .With(territoryId, pointOnFloor ?? averagePosition, 50f, fly: fly,
-                    ignoreDistanceToObject: true));
+            _taskQueue.Enqueue(_moveFactory.Move(new MoveTo.MoveParams(territoryId, pointOnFloor ?? averagePosition,
+                50f,
+                Fly: fly, IgnoreDistanceToObject: true)));
         }
 
-        _taskQueue.Enqueue(_serviceProvider.GetRequiredService<MoveToLandingLocation>()
-            .With(territoryId, fly, currentNode));
-        _taskQueue.Enqueue(_serviceProvider.GetRequiredService<Interact.DoInteract>()
-            .With(currentNode.DataId, null, EInteractionType.InternalGather, true));
+        _taskQueue.Enqueue(new MoveToLandingLocation(territoryId, fly, currentNode, _moveFactory, _gameFunctions,
+            _objectTable, _loggerFactory.CreateLogger<MoveToLandingLocation>()));
+        _taskQueue.Enqueue(_interactFactory.Interact(currentNode.DataId, null, EInteractionType.InternalGather, true));
 
+        QueueGatherNode(currentNode);
+    }
+
+    private void QueueGatherNode(GatheringNode currentNode)
+    {
         foreach (bool revisitRequired in new[] { false, true })
         {
-            _taskQueue.Enqueue(_serviceProvider.GetRequiredService<DoGather>()
-                .With(_currentRequest.Data, currentNode, revisitRequired));
+            _taskQueue.Enqueue(new DoGather(_currentRequest!.Data, currentNode, revisitRequired, this, _gameFunctions,
+                _gameGui, _clientState, _condition, _loggerFactory.CreateLogger<DoGather>()));
             if (_currentRequest.Data.Collectability > 0)
             {
-                _taskQueue.Enqueue(_serviceProvider.GetRequiredService<DoGatherCollectable>()
-                    .With(_currentRequest.Data, currentNode, revisitRequired));
+                _taskQueue.Enqueue(new DoGatherCollectable(_currentRequest.Data, currentNode, revisitRequired, this,
+                    _gameFunctions, _clientState, _gameGui, _loggerFactory.CreateLogger<DoGatherCollectable>()));
             }
         }
     }
diff --git a/Questionable/Controller/Steps/Common/Mount.cs b/Questionable/Controller/Steps/Common/Mount.cs
new file mode 100644 (file)
index 0000000..144de5b
--- /dev/null
@@ -0,0 +1,183 @@
+using System;
+using Dalamud.Game.ClientState.Conditions;
+using Dalamud.Plugin.Services;
+using FFXIVClientStructs.FFXIV.Client.Game;
+using FFXIVClientStructs.FFXIV.Common.Math;
+using Microsoft.Extensions.Logging;
+using Questionable.Data;
+using Questionable.Functions;
+
+namespace Questionable.Controller.Steps.Common;
+
+internal static class Mount
+{
+    internal sealed class Factory(
+        GameFunctions gameFunctions,
+        ICondition condition,
+        TerritoryData territoryData,
+        IClientState clientState,
+        ILoggerFactory loggerFactory)
+    {
+        public ITask Mount(ushort territoryId, EMountIf mountIf, Vector3? position = null)
+        {
+            if (mountIf == EMountIf.AwayFromPosition)
+                ArgumentNullException.ThrowIfNull(position);
+
+            return new MountTask(territoryId, mountIf, position, gameFunctions, condition, territoryData, clientState,
+                loggerFactory.CreateLogger<MountTask>());
+        }
+
+        public ITask Unmount()
+        {
+            return new UnmountTask(condition, loggerFactory.CreateLogger<UnmountTask>(), gameFunctions);
+        }
+    }
+
+    private sealed class MountTask(
+        ushort territoryId,
+        EMountIf mountIf,
+        Vector3? position,
+        GameFunctions gameFunctions,
+        ICondition condition,
+        TerritoryData territoryData,
+        IClientState clientState,
+        ILogger<MountTask> logger) : ITask
+    {
+        private bool _mountTriggered;
+        private DateTime _retryAt = DateTime.MinValue;
+
+        public bool Start()
+        {
+            if (condition[ConditionFlag.Mounted])
+                return false;
+
+            if (!territoryData.CanUseMount(territoryId))
+            {
+                logger.LogInformation("Can't use mount in current territory {Id}", territoryId);
+                return false;
+            }
+
+            if (gameFunctions.HasStatusPreventingMount())
+            {
+                logger.LogInformation("Can't mount due to status preventing sprint or mount");
+                return false;
+            }
+
+            if (mountIf == EMountIf.AwayFromPosition)
+            {
+                Vector3 playerPosition = clientState.LocalPlayer?.Position ?? Vector3.Zero;
+                float distance = System.Numerics.Vector3.Distance(playerPosition, position.GetValueOrDefault());
+                if (territoryId == clientState.TerritoryType && distance < 30f && !Conditions.IsDiving)
+                {
+                    logger.LogInformation("Not using mount, as we're close to the target");
+                    return false;
+                }
+
+                logger.LogInformation(
+                    "Want to use mount if away from destination ({Distance} yalms), trying (in territory {Id})...",
+                    distance, territoryId);
+            }
+            else
+                logger.LogInformation("Want to use mount, trying (in territory {Id})...", territoryId);
+
+            if (!condition[ConditionFlag.InCombat])
+            {
+                _retryAt = DateTime.Now.AddSeconds(0.5);
+                return true;
+            }
+
+            return false;
+        }
+
+        public ETaskResult Update()
+        {
+            if (_mountTriggered && !condition[ConditionFlag.Mounted] && DateTime.Now > _retryAt)
+            {
+                logger.LogInformation("Not mounted, retrying...");
+                _mountTriggered = false;
+                _retryAt = DateTime.MaxValue;
+            }
+
+            if (!_mountTriggered)
+            {
+                if (gameFunctions.HasStatusPreventingMount())
+                {
+                    logger.LogInformation("Can't mount due to status preventing sprint or mount");
+                    return ETaskResult.TaskComplete;
+                }
+
+                _mountTriggered = gameFunctions.Mount();
+                _retryAt = DateTime.Now.AddSeconds(5);
+                return ETaskResult.StillRunning;
+            }
+
+            return condition[ConditionFlag.Mounted]
+                ? ETaskResult.TaskComplete
+                : ETaskResult.StillRunning;
+        }
+
+        public override string ToString() => "Mount";
+    }
+
+    private sealed class UnmountTask(ICondition condition, ILogger<UnmountTask> logger, GameFunctions gameFunctions)
+        : ITask
+    {
+        private bool _unmountTriggered;
+        private DateTime _continueAt = DateTime.MinValue;
+
+        public bool Start()
+        {
+            if (!condition[ConditionFlag.Mounted])
+                return false;
+
+            logger.LogInformation("Step explicitly wants no mount, trying to unmount...");
+            if (condition[ConditionFlag.InFlight])
+            {
+                gameFunctions.Unmount();
+                _continueAt = DateTime.Now.AddSeconds(1);
+                return true;
+            }
+
+            _unmountTriggered = gameFunctions.Unmount();
+            _continueAt = DateTime.Now.AddSeconds(1);
+            return true;
+        }
+
+        public ETaskResult Update()
+        {
+            if (_continueAt >= DateTime.Now)
+                return ETaskResult.StillRunning;
+
+            if (!_unmountTriggered)
+            {
+                // if still flying, we still need to land
+                if (condition[ConditionFlag.InFlight])
+                    gameFunctions.Unmount();
+                else
+                    _unmountTriggered = gameFunctions.Unmount();
+
+                _continueAt = DateTime.Now.AddSeconds(1);
+                return ETaskResult.StillRunning;
+            }
+
+            if (condition[ConditionFlag.Mounted] && condition[ConditionFlag.InCombat])
+            {
+                _unmountTriggered = gameFunctions.Unmount();
+                _continueAt = DateTime.Now.AddSeconds(1);
+                return ETaskResult.StillRunning;
+            }
+
+            return condition[ConditionFlag.Mounted]
+                ? ETaskResult.StillRunning
+                : ETaskResult.TaskComplete;
+        }
+
+        public override string ToString() => "Unmount";
+    }
+
+    public enum EMountIf
+    {
+        Always,
+        AwayFromPosition,
+    }
+}
diff --git a/Questionable/Controller/Steps/Common/MountTask.cs b/Questionable/Controller/Steps/Common/MountTask.cs
deleted file mode 100644 (file)
index 6dfba0b..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-using System;
-using System.Numerics;
-using Dalamud.Game.ClientState.Conditions;
-using Dalamud.Plugin.Services;
-using FFXIVClientStructs.FFXIV.Client.Game;
-using Microsoft.Extensions.Logging;
-using Questionable.Data;
-using Questionable.Functions;
-
-namespace Questionable.Controller.Steps.Common;
-
-internal sealed class MountTask(
-    GameFunctions gameFunctions,
-    ICondition condition,
-    TerritoryData territoryData,
-    IClientState clientState,
-    ILogger<MountTask> logger) : ITask
-{
-    private ushort _territoryId;
-    private EMountIf _mountIf;
-    private Vector3? _position;
-
-    private bool _mountTriggered;
-    private DateTime _retryAt = DateTime.MinValue;
-
-    public ITask With(ushort territoryId, EMountIf mountIf, Vector3? position = null)
-    {
-        _territoryId = territoryId;
-        _mountIf = mountIf;
-        _position = position;
-
-        if (_mountIf == EMountIf.AwayFromPosition)
-            ArgumentNullException.ThrowIfNull(position);
-        return this;
-    }
-
-    public bool Start()
-    {
-        if (condition[ConditionFlag.Mounted])
-            return false;
-
-        if (!territoryData.CanUseMount(_territoryId))
-        {
-            logger.LogInformation("Can't use mount in current territory {Id}", _territoryId);
-            return false;
-        }
-
-        if (gameFunctions.HasStatusPreventingMount())
-        {
-            logger.LogInformation("Can't mount due to status preventing sprint or mount");
-            return false;
-        }
-
-        if (_mountIf == EMountIf.AwayFromPosition)
-        {
-            Vector3 playerPosition = clientState.LocalPlayer?.Position ?? Vector3.Zero;
-            float distance = (playerPosition - _position.GetValueOrDefault()).Length();
-            if (_territoryId == clientState.TerritoryType && distance < 30f && !Conditions.IsDiving)
-            {
-                logger.LogInformation("Not using mount, as we're close to the target");
-                return false;
-            }
-
-            logger.LogInformation(
-                "Want to use mount if away from destination ({Distance} yalms), trying (in territory {Id})...",
-                distance, _territoryId);
-        }
-        else
-            logger.LogInformation("Want to use mount, trying (in territory {Id})...", _territoryId);
-
-        if (!condition[ConditionFlag.InCombat])
-        {
-            _retryAt = DateTime.Now.AddSeconds(0.5);
-            return true;
-        }
-
-        return false;
-    }
-
-    public ETaskResult Update()
-    {
-        if (_mountTriggered && !condition[ConditionFlag.Mounted] && DateTime.Now > _retryAt)
-        {
-            logger.LogInformation("Not mounted, retrying...");
-            _mountTriggered = false;
-            _retryAt = DateTime.MaxValue;
-        }
-
-        if (!_mountTriggered)
-        {
-            if (gameFunctions.HasStatusPreventingMount())
-            {
-                logger.LogInformation("Can't mount due to status preventing sprint or mount");
-                return ETaskResult.TaskComplete;
-            }
-
-            _mountTriggered = gameFunctions.Mount();
-            _retryAt = DateTime.Now.AddSeconds(5);
-            return ETaskResult.StillRunning;
-        }
-
-        return condition[ConditionFlag.Mounted]
-            ? ETaskResult.TaskComplete
-            : ETaskResult.StillRunning;
-    }
-
-    public override string ToString() => "Mount";
-
-    public enum EMountIf
-    {
-        Always,
-        AwayFromPosition,
-    }
-}
index f338c88cc73703555179113d4610ac474764aa1e..e7fd2b626576d2f7b3dc8f2299573d9c1b18107d 100644 (file)
@@ -1,6 +1,4 @@
-using System;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging;
 using Questionable.Functions;
 using Questionable.Model;
 using Questionable.Model.Questing;
@@ -9,7 +7,7 @@ namespace Questionable.Controller.Steps.Common;
 
 internal static class NextQuest
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+    internal sealed class Factory(QuestRegistry questRegistry, QuestController questController, QuestFunctions questFunctions, ILoggerFactory loggerFactory) : SimpleTaskFactory
     {
         public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
@@ -22,37 +20,26 @@ internal static class NextQuest
             if (step.NextQuestId == quest.Id)
                 return null;
 
-            return serviceProvider.GetRequiredService<SetQuest>()
-                .With(step.NextQuestId, quest.Id);
+            return new SetQuest(step.NextQuestId, quest.Id, questRegistry, questController, questFunctions, loggerFactory.CreateLogger<SetQuest>());
         }
     }
 
-    internal sealed class SetQuest(QuestRegistry questRegistry, QuestController questController, QuestFunctions questFunctions, ILogger<SetQuest> logger) : ITask
+    private sealed class SetQuest(ElementId nextQuestId, ElementId currentQuestId, QuestRegistry questRegistry, QuestController questController, QuestFunctions questFunctions, ILogger<SetQuest> logger) : ITask
     {
-        public ElementId NextQuestId { get; set; } = null!;
-        public ElementId CurrentQuestId { get; set; } = null!;
-
-        public ITask With(ElementId nextQuestId, ElementId currentQuestId)
-        {
-            NextQuestId = nextQuestId;
-            CurrentQuestId = currentQuestId;
-            return this;
-        }
-
         public bool Start()
         {
-            if (questFunctions.IsQuestLocked(NextQuestId, CurrentQuestId))
+            if (questFunctions.IsQuestLocked(nextQuestId, currentQuestId))
             {
-                logger.LogInformation("Can't set next quest to {QuestId}, quest is locked", NextQuestId);
+                logger.LogInformation("Can't set next quest to {QuestId}, quest is locked", nextQuestId);
             }
-            else if (questRegistry.TryGetQuest(NextQuestId, out Quest? quest))
+            else if (questRegistry.TryGetQuest(nextQuestId, out Quest? quest))
             {
-                logger.LogInformation("Setting next quest to {QuestId}: '{QuestName}'", NextQuestId, quest.Info.Name);
+                logger.LogInformation("Setting next quest to {QuestId}: '{QuestName}'", nextQuestId, quest.Info.Name);
                 questController.SetNextQuest(quest);
             }
             else
             {
-                logger.LogInformation("Next quest with id {QuestId} not found", NextQuestId);
+                logger.LogInformation("Next quest with id {QuestId} not found", nextQuestId);
                 questController.SetNextQuest(null);
             }
 
@@ -61,6 +48,6 @@ internal static class NextQuest
 
         public ETaskResult Update() => ETaskResult.TaskComplete;
 
-        public override string ToString() => $"SetNextQuest({NextQuestId})";
+        public override string ToString() => $"SetNextQuest({nextQuestId})";
     }
 }
diff --git a/Questionable/Controller/Steps/Common/UnmountTask.cs b/Questionable/Controller/Steps/Common/UnmountTask.cs
deleted file mode 100644 (file)
index a379dc9..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-using System;
-using Dalamud.Game.ClientState.Conditions;
-using Dalamud.Plugin.Services;
-using Microsoft.Extensions.Logging;
-using Questionable.Functions;
-
-namespace Questionable.Controller.Steps.Common;
-
-internal sealed class UnmountTask(ICondition condition, ILogger<UnmountTask> logger, GameFunctions gameFunctions)
-    : ITask
-{
-    private bool _unmountTriggered;
-    private DateTime _continueAt = DateTime.MinValue;
-
-    public bool Start()
-    {
-        if (!condition[ConditionFlag.Mounted])
-            return false;
-
-        logger.LogInformation("Step explicitly wants no mount, trying to unmount...");
-        if (condition[ConditionFlag.InFlight])
-        {
-            gameFunctions.Unmount();
-            _continueAt = DateTime.Now.AddSeconds(1);
-            return true;
-        }
-
-        _unmountTriggered = gameFunctions.Unmount();
-        _continueAt = DateTime.Now.AddSeconds(1);
-        return true;
-    }
-
-    public ETaskResult Update()
-    {
-        if (_continueAt >= DateTime.Now)
-            return ETaskResult.StillRunning;
-
-        if (!_unmountTriggered)
-        {
-            // if still flying, we still need to land
-            if (condition[ConditionFlag.InFlight])
-                gameFunctions.Unmount();
-            else
-                _unmountTriggered = gameFunctions.Unmount();
-
-            _continueAt = DateTime.Now.AddSeconds(1);
-            return ETaskResult.StillRunning;
-        }
-
-        if (condition[ConditionFlag.Mounted] && condition[ConditionFlag.InCombat])
-        {
-            _unmountTriggered = gameFunctions.Unmount();
-            _continueAt = DateTime.Now.AddSeconds(1);
-            return ETaskResult.StillRunning;
-        }
-
-        return condition[ConditionFlag.Mounted]
-            ? ETaskResult.StillRunning
-            : ETaskResult.TaskComplete;
-    }
-
-    public override string ToString() => "Unmount";
-}
index 6cd4b3e702f386fea4fb0038263adbf1d604aea2..5eb29bd7a5962bd34f66d5df50ffb5eece1fd61e 100644 (file)
@@ -17,6 +17,9 @@ using Questionable.Model.Questing;
 namespace Questionable.Controller.Steps.Gathering;
 
 internal sealed class DoGather(
+    GatheringController.GatheringRequest currentRequest,
+    GatheringNode currentNode,
+    bool revisitRequired,
     GatheringController gatheringController,
     GameFunctions gameFunctions,
     IGameGui gameGui,
@@ -26,34 +29,22 @@ internal sealed class DoGather(
 {
     private const uint StatusGatheringRateUp = 218;
 
-    private GatheringController.GatheringRequest _currentRequest = null!;
-    private GatheringNode _currentNode = null!;
-    private bool _revisitRequired;
     private bool _revisitTriggered;
     private bool _wasGathering;
     private SlotInfo? _slotToGather;
     private Queue<EAction>? _actionQueue;
 
-    public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode,
-        bool revisitRequired)
-    {
-        _currentRequest = currentRequest;
-        _currentNode = currentNode;
-        _revisitRequired = revisitRequired;
-        return this;
-    }
-
     public bool Start() => true;
 
     public unsafe ETaskResult Update()
     {
-        if (_revisitRequired && !_revisitTriggered)
+        if (revisitRequired && !_revisitTriggered)
         {
             logger.LogInformation("No revisit");
             return ETaskResult.TaskComplete;
         }
 
-        if (gatheringController.HasNodeDisappeared(_currentNode))
+        if (gatheringController.HasNodeDisappeared(currentNode))
         {
             logger.LogInformation("Node disappeared");
             return ETaskResult.TaskComplete;
@@ -78,9 +69,9 @@ internal sealed class DoGather(
                 else
                 {
                     var slots = ReadSlots(addonGathering);
-                    if (_currentRequest.Collectability > 0)
+                    if (currentRequest.Collectability > 0)
                     {
-                        var slot = slots.Single(x => x.ItemId == _currentRequest.ItemId);
+                        var slot = slots.Single(x => x.ItemId == currentRequest.ItemId);
                         addonGathering->FireCallbackInt(slot.Index);
                     }
                     else
@@ -103,7 +94,7 @@ internal sealed class DoGather(
                         _actionQueue = GetNextActions(nodeCondition, slots);
                         if (_actionQueue.Count == 0)
                         {
-                            var slot = _slotToGather ?? slots.Single(x => x.ItemId == _currentRequest.ItemId);
+                            var slot = _slotToGather ?? slots.Single(x => x.ItemId == currentRequest.ItemId);
                             addonGathering->FireCallbackInt(slot.Index);
                         }
                     }
@@ -157,9 +148,9 @@ internal sealed class DoGather(
         if (!gameFunctions.HasStatus(StatusGatheringRateUp))
         {
             // do we have an alternative item? only happens for 'evaluation' leve quests
-            if (_currentRequest.AlternativeItemId != 0)
+            if (currentRequest.AlternativeItemId != 0)
             {
-                var alternativeSlot = slots.Single(x => x.ItemId == _currentRequest.AlternativeItemId);
+                var alternativeSlot = slots.Single(x => x.ItemId == currentRequest.AlternativeItemId);
 
                 if (alternativeSlot.GatheringChance == 100)
                 {
@@ -195,7 +186,7 @@ internal sealed class DoGather(
                 }
             }
 
-            var slot = slots.Single(x => x.ItemId == _currentRequest.ItemId);
+            var slot = slots.Single(x => x.ItemId == currentRequest.ItemId);
             if (slot.GatheringChance > 0 && slot.GatheringChance < 100)
             {
                 if (slot.GatheringChance >= 95 &&
@@ -243,7 +234,7 @@ internal sealed class DoGather(
         _revisitTriggered = true;
     }
 
-    public override string ToString() => $"DoGather{(_revisitRequired ? " if revist" : "")}";
+    public override string ToString() => $"DoGather{(revisitRequired ? " if revist" : "")}";
 
     private sealed record SlotInfo(int Index, uint ItemId, int GatheringChance, int BoonChance, int Quantity);
 
index 3c6f776d6c25d1aa945ba7be1cceb380f502a483..99779c140095ed4bdcf62ba3d752d4d610ae309a 100644 (file)
@@ -14,40 +14,31 @@ using Questionable.Model.Questing;
 namespace Questionable.Controller.Steps.Gathering;
 
 internal sealed class DoGatherCollectable(
+    GatheringController.GatheringRequest currentRequest,
+    GatheringNode currentNode,
+    bool revisitRequired,
     GatheringController gatheringController,
     GameFunctions gameFunctions,
     IClientState clientState,
     IGameGui gameGui,
     ILogger<DoGatherCollectable> logger) : ITask, IRevisitAware
 {
-    private GatheringController.GatheringRequest _currentRequest = null!;
-    private GatheringNode _currentNode = null!;
-    private bool _revisitRequired;
     private bool _revisitTriggered;
     private Queue<EAction>? _actionQueue;
 
     private bool? _expectedScrutiny;
 
-    public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode,
-        bool revisitRequired)
-    {
-        _currentRequest = currentRequest;
-        _currentNode = currentNode;
-        _revisitRequired = revisitRequired;
-        return this;
-    }
-
     public bool Start() => true;
 
     public unsafe ETaskResult Update()
     {
-        if (_revisitRequired && !_revisitTriggered)
+        if (revisitRequired && !_revisitTriggered)
         {
             logger.LogInformation("No revisit");
             return ETaskResult.TaskComplete;
         }
 
-        if (gatheringController.HasNodeDisappeared(_currentNode))
+        if (gatheringController.HasNodeDisappeared(currentNode))
         {
             logger.LogInformation("Node disappeared");
             return ETaskResult.TaskComplete;
@@ -103,7 +94,7 @@ internal sealed class DoGatherCollectable(
             return ETaskResult.StillRunning;
         }
 
-        if (nodeCondition.CollectabilityToGoal(_currentRequest.Collectability) > 0)
+        if (nodeCondition.CollectabilityToGoal(currentRequest.Collectability) > 0)
         {
             _actionQueue = GetNextActions(nodeCondition);
             if (_actionQueue != null)
@@ -147,7 +138,7 @@ internal sealed class DoGatherCollectable(
 
         Queue<EAction> actions = new();
 
-        uint neededCollectability = nodeCondition.CollectabilityToGoal(_currentRequest.Collectability);
+        uint neededCollectability = nodeCondition.CollectabilityToGoal(currentRequest.Collectability);
         if (neededCollectability <= nodeCondition.CollectabilityFromMeticulous)
         {
             logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ meticulous",
@@ -203,7 +194,7 @@ internal sealed class DoGatherCollectable(
     }
 
     public override string ToString() =>
-        $"DoGatherCollectable({SeIconChar.Collectible.ToIconString()}/{_expectedScrutiny} {_currentRequest.Collectability}){(_revisitRequired ? " if revist" : "")}";
+        $"DoGatherCollectable({SeIconChar.Collectible.ToIconString()}/{_expectedScrutiny} {currentRequest.Collectability}){(revisitRequired ? " if revist" : "")}";
 
     [SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")]
     private sealed record NodeCondition(
index 84b9c88896e7e8e648021609b921950ef016a780..5694d65ec696e8ae3ce1cd3b491fb2dfc94b0cc0 100644 (file)
@@ -1,5 +1,4 @@
-using System;
-using System.Globalization;
+using System.Globalization;
 using System.Linq;
 using System.Numerics;
 using Dalamud.Game.ClientState.Objects.Enums;
@@ -14,49 +13,40 @@ using Questionable.Model.Gathering;
 namespace Questionable.Controller.Steps.Gathering;
 
 internal sealed class MoveToLandingLocation(
-    IServiceProvider serviceProvider,
+    ushort territoryId,
+    bool flyBetweenNodes,
+    GatheringNode gatheringNode,
+    MoveTo.Factory moveFactory,
     GameFunctions gameFunctions,
     IObjectTable objectTable,
     ILogger<MoveToLandingLocation> logger) : ITask
 {
-    private ushort _territoryId;
-    private bool _flyBetweenNodes;
-    private GatheringNode _gatheringNode = null!;
     private ITask _moveTask = null!;
 
-    public ITask With(ushort territoryId, bool flyBetweenNodes, GatheringNode gatheringNode)
-    {
-        _territoryId = territoryId;
-        _flyBetweenNodes = flyBetweenNodes;
-        _gatheringNode = gatheringNode;
-        return this;
-    }
-
     public bool Start()
     {
-        var location = _gatheringNode.Locations.First();
-        if (_gatheringNode.Locations.Count > 1)
+        var location = gatheringNode.Locations.First();
+        if (gatheringNode.Locations.Count > 1)
         {
             var gameObject = objectTable.SingleOrDefault(x =>
-                x.ObjectKind == ObjectKind.GatheringPoint && x.DataId == _gatheringNode.DataId && x.IsTargetable);
+                x.ObjectKind == ObjectKind.GatheringPoint && x.DataId == gatheringNode.DataId && x.IsTargetable);
             if (gameObject == null)
                 return false;
 
-            location = _gatheringNode.Locations.Single(x => Vector3.Distance(x.Position, gameObject.Position) < 0.1f);
+            location = gatheringNode.Locations.Single(x => Vector3.Distance(x.Position, gameObject.Position) < 0.1f);
         }
 
         var (target, degrees, range) = GatheringMath.CalculateLandingLocation(location);
         logger.LogInformation("Preliminary landing location: {Location}, with degrees = {Degrees}, range = {Range}",
             target.ToString("G", CultureInfo.InvariantCulture), degrees, range);
 
-        bool fly = _flyBetweenNodes && gameFunctions.IsFlyingUnlocked(_territoryId);
-        _moveTask = serviceProvider.GetRequiredService<Move.MoveInternal>()
-            .With(_territoryId, target, 0.25f, dataId: _gatheringNode.DataId, fly: fly,
-                ignoreDistanceToObject: true);
+        bool fly = flyBetweenNodes && gameFunctions.IsFlyingUnlocked(territoryId);
+        _moveTask = moveFactory.Move(new MoveTo.MoveParams(territoryId, target, 0.25f, DataId: gatheringNode.DataId,
+            Fly: fly, IgnoreDistanceToObject: true));
         return _moveTask.Start();
     }
 
     public ETaskResult Update() => _moveTask.Update();
 
-    public override string ToString() => $"Land/{_moveTask}/{_flyBetweenNodes}";
+    public override string ToString() => $"Land/{_moveTask}/{flyBetweenNodes}";
 }
index 23abcb3ca128ef9d4c029fce0a3051f9d9b84edc..ca6a93d8bee178c6cee54406d573a24c9f265de4 100644 (file)
@@ -13,18 +13,18 @@ namespace Questionable.Controller.Steps.Gathering;
 
 internal static class TurnInDelivery
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+    internal sealed class Factory(ILoggerFactory loggerFactory) : SimpleTaskFactory
     {
         public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (quest.Id is not SatisfactionSupplyNpcId || sequence.Sequence != 1)
                 return null;
 
-            return serviceProvider.GetRequiredService<SatisfactionSupplyTurnIn>();
+            return new SatisfactionSupplyTurnIn(loggerFactory.CreateLogger<SatisfactionSupplyTurnIn>());
         }
     }
 
-    internal sealed class SatisfactionSupplyTurnIn(ILogger<SatisfactionSupplyTurnIn> logger) : ITask
+    private sealed class SatisfactionSupplyTurnIn(ILogger<SatisfactionSupplyTurnIn> logger) : ITask
     {
         private ushort? _remainingAllowances;
 
index 802d2d8c43ac9a23a214a23f63173eba2f7ca859..a91983b3166c24cfc793f49d46603c2b952cb9fb 100644 (file)
@@ -14,7 +14,8 @@ namespace Questionable.Controller.Steps.Interactions;
 
 internal static class Action
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class Factory(GameFunctions gameFunctions, Mount.Factory mountFactory, ILoggerFactory loggerFactory)
+        : ITaskFactory
     {
         public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
         {
@@ -23,54 +24,45 @@ internal static class Action
 
             ArgumentNullException.ThrowIfNull(step.Action);
 
-            var task = serviceProvider.GetRequiredService<UseOnObject>()
-                .With(step.DataId, step.Action.Value);
+            var task = new UseOnObject(step.DataId, step.Action.Value, gameFunctions,
+                loggerFactory.CreateLogger<UseOnObject>());
             if (step.Action.Value.RequiresMount())
                 return [task];
             else
-            {
-                var unmount = serviceProvider.GetRequiredService<UnmountTask>();
-                return [unmount, task];
-            }
+                return [mountFactory.Unmount(), task];
         }
     }
 
-    internal sealed class UseOnObject(GameFunctions gameFunctions, ILogger<UseOnObject> logger) : ITask
+    private sealed class UseOnObject(
+        uint? dataId,
+        EAction action,
+        GameFunctions gameFunctions,
+        ILogger<UseOnObject> logger) : ITask
     {
         private bool _usedAction;
         private DateTime _continueAt = DateTime.MinValue;
 
-        public uint? DataId { get; set; }
-        public EAction Action { get; set; }
-
-        public ITask With(uint? dataId, EAction action)
-        {
-            DataId = dataId;
-            Action = action;
-            return this;
-        }
-
         public bool Start()
         {
-            if (DataId != null)
+            if (dataId != null)
             {
-                IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId.Value);
+                IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId.Value);
                 if (gameObject == null)
                 {
-                    logger.LogWarning("No game object with dataId {DataId}", DataId);
+                    logger.LogWarning("No game object with dataId {DataId}", dataId);
                     return false;
                 }
 
                 if (gameObject.IsTargetable)
                 {
-                    _usedAction = gameFunctions.UseAction(gameObject, Action);
+                    _usedAction = gameFunctions.UseAction(gameObject, action);
                     _continueAt = DateTime.Now.AddSeconds(0.5);
                     return true;
                 }
             }
             else
             {
-                _usedAction = gameFunctions.UseAction(Action);
+                _usedAction = gameFunctions.UseAction(action);
                 _continueAt = DateTime.Now.AddSeconds(0.5);
                 return true;
             }
@@ -85,18 +77,18 @@ internal static class Action
 
             if (!_usedAction)
             {
-                if (DataId != null)
+                if (dataId != null)
                 {
-                    IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId.Value);
+                    IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId.Value);
                     if (gameObject == null || !gameObject.IsTargetable)
                         return ETaskResult.StillRunning;
 
-                    _usedAction = gameFunctions.UseAction(gameObject, Action);
+                    _usedAction = gameFunctions.UseAction(gameObject, action);
                     _continueAt = DateTime.Now.AddSeconds(0.5);
                 }
                 else
                 {
-                    _usedAction = gameFunctions.UseAction(Action);
+                    _usedAction = gameFunctions.UseAction(action);
                     _continueAt = DateTime.Now.AddSeconds(0.5);
                 }
 
@@ -106,6 +98,6 @@ internal static class Action
             return ETaskResult.TaskComplete;
         }
 
-        public override string ToString() => $"Action({Action})";
+        public override string ToString() => $"Action({action})";
     }
 }
index d43872885eb24ab81995a377e6718c13afa6846a..45507e80485389ac83c4709dbd32fb97e6f84892 100644 (file)
@@ -11,7 +11,11 @@ namespace Questionable.Controller.Steps.Interactions;
 
 internal static class AetherCurrent
 {
-    internal sealed class Factory(IServiceProvider serviceProvider, AetherCurrentData aetherCurrentData, IChatGui chatGui) : SimpleTaskFactory
+    internal sealed class Factory(
+        GameFunctions gameFunctions,
+        AetherCurrentData aetherCurrentData,
+        IChatGui chatGui,
+        ILoggerFactory loggerFactory) : SimpleTaskFactory
     {
         public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
@@ -23,47 +27,37 @@ internal static class AetherCurrent
 
             if (!aetherCurrentData.IsValidAetherCurrent(step.TerritoryId, step.AetherCurrentId.Value))
             {
-                chatGui.PrintError($"[Questionable] Aether current with id {step.AetherCurrentId} is referencing an invalid aether current, will skip attunement");
+                chatGui.PrintError(
+                    $"[Questionable] Aether current with id {step.AetherCurrentId} is referencing an invalid aether current, will skip attunement");
                 return null;
             }
 
-            return serviceProvider.GetRequiredService<DoAttune>()
-                .With(step.DataId.Value, step.AetherCurrentId.Value);
+            return new DoAttune(step.DataId.Value, step.AetherCurrentId.Value, gameFunctions, loggerFactory.CreateLogger<DoAttune>());
         }
     }
 
-    internal sealed class DoAttune(GameFunctions gameFunctions, ILogger<DoAttune> logger) : ITask
+    private sealed class DoAttune(uint dataId, uint aetherCurrentId, GameFunctions gameFunctions, ILogger<DoAttune> logger) : ITask
     {
-        public uint DataId { get; set; }
-        public uint AetherCurrentId { get; set; }
-
-        public ITask With(uint dataId, uint aetherCurrentId)
-        {
-            DataId = dataId;
-            AetherCurrentId = aetherCurrentId;
-            return this;
-        }
-
         public bool Start()
         {
-            if (!gameFunctions.IsAetherCurrentUnlocked(AetherCurrentId))
+            if (!gameFunctions.IsAetherCurrentUnlocked(aetherCurrentId))
             {
-                logger.LogInformation("Attuning to aether current {AetherCurrentId} / {DataId}", AetherCurrentId,
-                    DataId);
-                gameFunctions.InteractWith(DataId);
+                logger.LogInformation("Attuning to aether current {AetherCurrentId} / {DataId}", aetherCurrentId,
+                    dataId);
+                gameFunctions.InteractWith(dataId);
                 return true;
             }
 
-            logger.LogInformation("Already attuned to aether current {AetherCurrentId} / {DataId}", AetherCurrentId,
-                DataId);
+            logger.LogInformation("Already attuned to aether current {AetherCurrentId} / {DataId}", aetherCurrentId,
+                dataId);
             return false;
         }
 
         public ETaskResult Update() =>
-            gameFunctions.IsAetherCurrentUnlocked(AetherCurrentId)
+            gameFunctions.IsAetherCurrentUnlocked(aetherCurrentId)
                 ? ETaskResult.TaskComplete
                 : ETaskResult.StillRunning;
 
-        public override string ToString() => $"AttuneAetherCurrent({AetherCurrentId})";
+        public override string ToString() => $"AttuneAetherCurrent({aetherCurrentId})";
     }
 }
index 06079acb41b34187753e01efca068c483bb74d5c..edcf4720ef00c4a8bc88b5ada4feee1250bb767a 100644 (file)
@@ -11,7 +11,10 @@ namespace Questionable.Controller.Steps.Interactions;
 
 internal static class AethernetShard
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+    internal sealed class Factory(
+        AetheryteFunctions aetheryteFunctions,
+        GameFunctions gameFunctions,
+        ILoggerFactory loggerFactory) : SimpleTaskFactory
     {
         public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
@@ -20,42 +23,35 @@ internal static class AethernetShard
 
             ArgumentNullException.ThrowIfNull(step.AethernetShard);
 
-            return serviceProvider.GetRequiredService<DoAttune>()
-                .With(step.AethernetShard.Value);
+            return new DoAttune(step.AethernetShard.Value, aetheryteFunctions, gameFunctions,
+                loggerFactory.CreateLogger<DoAttune>());
         }
     }
 
-    internal sealed class DoAttune(
+    private sealed class DoAttune(
+        EAetheryteLocation aetheryteLocation,
         AetheryteFunctions aetheryteFunctions,
         GameFunctions gameFunctions,
         ILogger<DoAttune> logger) : ITask
     {
-        public EAetheryteLocation AetheryteLocation { get; set; }
-
-        public ITask With(EAetheryteLocation aetheryteLocation)
-        {
-            AetheryteLocation = aetheryteLocation;
-            return this;
-        }
-
         public bool Start()
         {
-            if (!aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation))
+            if (!aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation))
             {
-                logger.LogInformation("Attuning to aethernet shard {AethernetShard}", AetheryteLocation);
-                gameFunctions.InteractWith((uint)AetheryteLocation, ObjectKind.Aetheryte);
+                logger.LogInformation("Attuning to aethernet shard {AethernetShard}", aetheryteLocation);
+                gameFunctions.InteractWith((uint)aetheryteLocation, ObjectKind.Aetheryte);
                 return true;
             }
 
-            logger.LogInformation("Already attuned to aethernet shard {AethernetShard}", AetheryteLocation);
+            logger.LogInformation("Already attuned to aethernet shard {AethernetShard}", aetheryteLocation);
             return false;
         }
 
         public ETaskResult Update() =>
-            aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation)
+            aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation)
                 ? ETaskResult.TaskComplete
                 : ETaskResult.StillRunning;
 
-        public override string ToString() => $"AttuneAethernetShard({AetheryteLocation})";
+        public override string ToString() => $"AttuneAethernetShard({aetheryteLocation})";
     }
 }
index 6103374972435a7cdebc0010ebf70c7ae2a1d616..5af0a6a356d89c95ad0ab759da6008e42fdabf23 100644 (file)
@@ -10,7 +10,10 @@ namespace Questionable.Controller.Steps.Interactions;
 
 internal static class Aetheryte
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+    internal sealed class Factory(
+        AetheryteFunctions aetheryteFunctions,
+        GameFunctions gameFunctions,
+        ILoggerFactory loggerFactory) : SimpleTaskFactory
     {
         public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
@@ -19,42 +22,35 @@ internal static class Aetheryte
 
             ArgumentNullException.ThrowIfNull(step.Aetheryte);
 
-            return serviceProvider.GetRequiredService<DoAttune>()
-                .With(step.Aetheryte.Value);
+            return new DoAttune(step.Aetheryte.Value, aetheryteFunctions, gameFunctions,
+                loggerFactory.CreateLogger<DoAttune>());
         }
     }
 
-    internal sealed class DoAttune(
+    private sealed class DoAttune(
+        EAetheryteLocation aetheryteLocation,
         AetheryteFunctions aetheryteFunctions,
         GameFunctions gameFunctions,
         ILogger<DoAttune> logger) : ITask
     {
-        public EAetheryteLocation AetheryteLocation { get; set; }
-
-        public ITask With(EAetheryteLocation aetheryteLocation)
-        {
-            AetheryteLocation = aetheryteLocation;
-            return this;
-        }
-
         public bool Start()
         {
-            if (!aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation))
+            if (!aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation))
             {
-                logger.LogInformation("Attuning to aetheryte {Aetheryte}", AetheryteLocation);
-                gameFunctions.InteractWith((uint)AetheryteLocation);
+                logger.LogInformation("Attuning to aetheryte {Aetheryte}", aetheryteLocation);
+                gameFunctions.InteractWith((uint)aetheryteLocation);
                 return true;
             }
 
-            logger.LogInformation("Already attuned to aetheryte {Aetheryte}", AetheryteLocation);
+            logger.LogInformation("Already attuned to aetheryte {Aetheryte}", aetheryteLocation);
             return false;
         }
 
         public ETaskResult Update() =>
-            aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation)
+            aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation)
                 ? ETaskResult.TaskComplete
                 : ETaskResult.StillRunning;
 
-        public override string ToString() => $"AttuneAetheryte({AetheryteLocation})";
+        public override string ToString() => $"AttuneAetheryte({aetheryteLocation})";
     }
 }
index e3bfc2588fbf5da5bf4b0d9b89cee33a0a88510e..8776280aa62836c8a63f1a6a238e0d46e54330db 100644 (file)
@@ -13,7 +13,12 @@ namespace Questionable.Controller.Steps.Interactions;
 
 internal static class Combat
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class Factory(
+        CombatController combatController,
+        Interact.Factory interactFactory,
+        Mount.Factory mountFactory,
+        UseItem.Factory useItemFactory,
+        QuestFunctions questFunctions) : ITaskFactory
     {
         public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
         {
@@ -22,12 +27,11 @@ internal static class Combat
 
             ArgumentNullException.ThrowIfNull(step.EnemySpawnType);
 
-            yield return serviceProvider.GetRequiredService<UnmountTask>();
+            yield return mountFactory.Unmount();
 
             if (step.CombatDelaySecondsAtStart != null)
             {
-                yield return serviceProvider.GetRequiredService<WaitAtStart.WaitDelay>()
-                    .With(TimeSpan.FromSeconds(step.CombatDelaySecondsAtStart.Value));
+                yield return new WaitAtStart.WaitDelay(TimeSpan.FromSeconds(step.CombatDelaySecondsAtStart.Value));
             }
 
             switch (step.EnemySpawnType)
@@ -36,8 +40,7 @@ internal static class Combat
                 {
                     ArgumentNullException.ThrowIfNull(step.DataId);
 
-                    yield return serviceProvider.GetRequiredService<Interact.DoInteract>()
-                        .With(step.DataId.Value, quest, EInteractionType.None, true);
+                    yield return interactFactory.Interact(step.DataId.Value, quest, EInteractionType.None, true);
                     yield return CreateTask(quest, sequence, step);
                     break;
                 }
@@ -47,9 +50,8 @@ internal static class Combat
                     ArgumentNullException.ThrowIfNull(step.DataId);
                     ArgumentNullException.ThrowIfNull(step.ItemId);
 
-                    yield return serviceProvider.GetRequiredService<UseItem.UseOnObject>()
-                        .With(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags,
-                            true);
+                    yield return useItemFactory.OnObject(quest.Id, step.DataId.Value, step.ItemId.Value,
+                        step.CompletionQuestVariablesFlags, true);
                     yield return CreateTask(quest, sequence, step);
                     break;
                 }
@@ -73,34 +75,32 @@ internal static class Combat
             ArgumentNullException.ThrowIfNull(step.EnemySpawnType);
 
             bool isLastStep = sequence.Steps.Last() == step;
-            return serviceProvider.GetRequiredService<HandleCombat>()
-                .With(quest.Id, isLastStep, step.EnemySpawnType.Value, step.KillEnemyDataIds,
-                    step.CompletionQuestVariablesFlags, step.ComplexCombatData);
+            return CreateTask(quest.Id, isLastStep, step.EnemySpawnType.Value, step.KillEnemyDataIds,
+                step.CompletionQuestVariablesFlags, step.ComplexCombatData);
         }
-    }
-
-    internal sealed class HandleCombat(CombatController combatController, QuestFunctions questFunctions) : ITask
-    {
-        private bool _isLastStep;
-        private CombatController.CombatData _combatData = null!;
-        private IList<QuestWorkValue?> _completionQuestVariableFlags = null!;
 
-        public ITask With(ElementId elementId, bool isLastStep, EEnemySpawnType enemySpawnType, IList<uint> killEnemyDataIds,
-            IList<QuestWorkValue?> completionQuestVariablesFlags, IList<ComplexCombatData> complexCombatData)
+        private HandleCombat CreateTask(ElementId elementId, bool isLastStep, EEnemySpawnType enemySpawnType,
+            IList<uint> killEnemyDataIds, IList<QuestWorkValue?> completionQuestVariablesFlags,
+            IList<ComplexCombatData> complexCombatData)
         {
-            _isLastStep = isLastStep;
-            _combatData = new CombatController.CombatData
+            return new HandleCombat(isLastStep, new CombatController.CombatData
             {
                 ElementId = elementId,
                 SpawnType = enemySpawnType,
                 KillEnemyDataIds = killEnemyDataIds.ToList(),
                 ComplexCombatDatas = complexCombatData.ToList(),
-            };
-            _completionQuestVariableFlags = completionQuestVariablesFlags;
-            return this;
+            }, completionQuestVariablesFlags, combatController, questFunctions);
         }
+    }
 
-        public bool Start() => combatController.Start(_combatData);
+    private sealed class HandleCombat(
+        bool isLastStep,
+        CombatController.CombatData combatData,
+        IList<QuestWorkValue?> completionQuestVariableFlags,
+        CombatController combatController,
+        QuestFunctions questFunctions) : ITask
+    {
+        public bool Start() => combatController.Start(combatData);
 
         public ETaskResult Update()
         {
@@ -108,13 +108,14 @@ internal static class Combat
                 return ETaskResult.StillRunning;
 
             // if our quest step has any completion flags, we need to check if they are set
-            if (QuestWorkUtils.HasCompletionFlags(_completionQuestVariableFlags) && _combatData.ElementId is QuestId questId)
+            if (QuestWorkUtils.HasCompletionFlags(completionQuestVariableFlags) &&
+                combatData.ElementId is QuestId questId)
             {
                 var questWork = questFunctions.GetQuestProgressInfo(questId);
                 if (questWork == null)
                     return ETaskResult.StillRunning;
 
-                if (QuestWorkUtils.MatchesQuestWork(_completionQuestVariableFlags, questWork))
+                if (QuestWorkUtils.MatchesQuestWork(completionQuestVariableFlags, questWork))
                     return ETaskResult.TaskComplete;
                 else
                     return ETaskResult.StillRunning;
@@ -122,7 +123,7 @@ internal static class Combat
 
             // the last step, by definition, can only be progressed by the game recognizing we're in a new sequence,
             // so this is an indefinite wait
-            if (_isLastStep)
+            if (isLastStep)
                 return ETaskResult.StillRunning;
             else
             {
@@ -133,9 +134,9 @@ internal static class Combat
 
         public override string ToString()
         {
-            if (QuestWorkUtils.HasCompletionFlags(_completionQuestVariableFlags))
+            if (QuestWorkUtils.HasCompletionFlags(completionQuestVariableFlags))
                 return "HandleCombat(wait: QW flags)";
-            else if (_isLastStep)
+            else if (isLastStep)
                 return "HandleCombat(wait: next sequence)";
             else
                 return "HandleCombat(wait: not in combat)";
index 3b8712f49b5feb38c9da5d8f9f75ef9577f05209..0fb2d3a6eda080d03e8a24f151297616d28c9f46 100644 (file)
@@ -18,18 +18,23 @@ namespace Questionable.Controller.Steps.Interactions;
 
 internal static class Dive
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+    internal sealed class Factory(ICondition condition, ILoggerFactory loggerFactory) : SimpleTaskFactory
     {
         public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (step.InteractionType != EInteractionType.Dive)
                 return null;
 
-            return serviceProvider.GetRequiredService<DoDive>();
+            return Dive();
+        }
+
+        public ITask Dive()
+        {
+            return new DoDive(condition, loggerFactory.CreateLogger<DoDive>());
         }
     }
 
-    internal sealed class DoDive(ICondition condition, ILogger<DoDive> logger)
+    private sealed class DoDive(ICondition condition, ILogger<DoDive> logger)
         : AbstractDelayedTask(TimeSpan.FromSeconds(5))
     {
         private readonly Queue<(uint Type, nint Key)> _keysToPress = [];
index ed80dd4cc3440e7275666622acd6567c92d31049..975d107e5f532a8a2db80c2fba9a6a73fea33069 100644 (file)
@@ -10,7 +10,7 @@ namespace Questionable.Controller.Steps.Interactions;
 
 internal static class Duty
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+    internal sealed class Factory(GameFunctions gameFunctions, ICondition condition) : SimpleTaskFactory
     {
         public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
@@ -18,33 +18,26 @@ internal static class Duty
                 return null;
 
             ArgumentNullException.ThrowIfNull(step.ContentFinderConditionId);
-
-            return serviceProvider.GetRequiredService<OpenDutyFinder>()
-                .With(step.ContentFinderConditionId.Value);
+            return new OpenDutyFinder(step.ContentFinderConditionId.Value, gameFunctions, condition);
         }
     }
 
-    internal sealed class OpenDutyFinder(GameFunctions gameFunctions, ICondition condition) : ITask
+    private sealed class OpenDutyFinder(
+        uint contentFinderConditionId,
+        GameFunctions gameFunctions,
+        ICondition condition) : ITask
     {
-        public uint ContentFinderConditionId { get; set; }
-
-        public ITask With(uint contentFinderConditionId)
-        {
-            ContentFinderConditionId = contentFinderConditionId;
-            return this;
-        }
-
         public bool Start()
         {
             if (condition[ConditionFlag.InDutyQueue])
                 return false;
 
-            gameFunctions.OpenDutyFinder(ContentFinderConditionId);
+            gameFunctions.OpenDutyFinder(contentFinderConditionId);
             return true;
         }
 
         public ETaskResult Update() => ETaskResult.TaskComplete;
 
-        public override string ToString() => $"OpenDutyFinder({ContentFinderConditionId})";
+        public override string ToString() => $"OpenDutyFinder({contentFinderConditionId})";
     }
 }
index 35cd64b7cedd89b30a1759fbdcd3faa76297168a..940aabb15ecc918b628bf06673eb1055214e3bc2 100644 (file)
@@ -10,7 +10,7 @@ namespace Questionable.Controller.Steps.Interactions;
 
 internal static class Emote
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class Factory(ChatFunctions chatFunctions, Mount.Factory mountFactory) : ITaskFactory
     {
         public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
         {
@@ -24,57 +24,39 @@ internal static class Emote
 
             ArgumentNullException.ThrowIfNull(step.Emote);
 
-            var unmount = serviceProvider.GetRequiredService<UnmountTask>();
+            var unmount = mountFactory.Unmount();
             if (step.DataId != null)
             {
-                var task = serviceProvider.GetRequiredService<UseOnObject>().With(step.Emote.Value, step.DataId.Value);
+                var task = new UseOnObject(step.Emote.Value, step.DataId.Value, chatFunctions);
                 return [unmount, task];
             }
             else
             {
-                var task = serviceProvider.GetRequiredService<Use>().With(step.Emote.Value);
+                var task = new UseOnSelf(step.Emote.Value, chatFunctions);
                 return [unmount, task];
             }
         }
     }
 
-    internal sealed class UseOnObject(ChatFunctions chatFunctions) : AbstractDelayedTask
+    private sealed class UseOnObject(EEmote emote, uint dataId, ChatFunctions chatFunctions) : AbstractDelayedTask
     {
-        public EEmote Emote { get; set; }
-        public uint DataId { get; set; }
-
-        public ITask With(EEmote emote, uint dataId)
-        {
-            Emote = emote;
-            DataId = dataId;
-            return this;
-        }
-
         protected override bool StartInternal()
         {
-            chatFunctions.UseEmote(DataId, Emote);
+            chatFunctions.UseEmote(dataId, emote);
             return true;
         }
 
-        public override string ToString() => $"Emote({Emote} on {DataId})";
+        public override string ToString() => $"Emote({emote} on {dataId})";
     }
 
-    internal sealed class Use(ChatFunctions chatFunctions) : AbstractDelayedTask
+    private sealed class UseOnSelf(EEmote emote, ChatFunctions chatFunctions) : AbstractDelayedTask
     {
-        public EEmote Emote { get; set; }
-
-        public ITask With(EEmote emote)
-        {
-            Emote = emote;
-            return this;
-        }
-
         protected override bool StartInternal()
         {
-            chatFunctions.UseEmote(Emote);
+            chatFunctions.UseEmote(emote);
             return true;
         }
 
-        public override string ToString() => $"Emote({Emote})";
+        public override string ToString() => $"Emote({emote})";
     }
 }
index abd72c7cb926281edc0ea127333038648a074736..4adea07958a62c73f46ab1a6232998d922a874f9 100644 (file)
@@ -16,7 +16,7 @@ namespace Questionable.Controller.Steps.Interactions;
 
 internal static class EquipItem
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+    internal sealed class Factory(IDataManager dataManager, ILoggerFactory loggerFactory) : SimpleTaskFactory
     {
         public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
@@ -24,14 +24,39 @@ internal static class EquipItem
                 return null;
 
             ArgumentNullException.ThrowIfNull(step.ItemId);
-            return serviceProvider.GetRequiredService<DoEquip>()
-                .With(step.ItemId.Value);
+            return Equip(step.ItemId.Value);
+        }
+
+        private DoEquip Equip(uint itemId)
+        {
+            var item = dataManager.GetExcelSheet<Item>()!.GetRow(itemId) ??
+                       throw new ArgumentOutOfRangeException(nameof(itemId));
+            var targetSlots = GetEquipSlot(item) ?? throw new InvalidOperationException("Not a piece of equipment");
+            return new DoEquip(itemId, item, targetSlots, dataManager, loggerFactory.CreateLogger<DoEquip>());
+        }
+
+        private static List<ushort>? GetEquipSlot(Item item)
+        {
+            return item.EquipSlotCategory.Row switch
+            {
+                >= 1 and <= 11 => [(ushort)(item.EquipSlotCategory.Row - 1)],
+                12 => [11, 12], // rings
+                13 => [0],
+                17 => [13], // soul crystal
+                _ => null
+            };
         }
     }
 
-    internal sealed class DoEquip(IDataManager dataManager, ILogger<DoEquip> logger) : ITask, IToastAware
+    private sealed class DoEquip(
+        uint itemId,
+        Item item,
+        List<ushort> targetSlots,
+        IDataManager dataManager,
+        ILogger<DoEquip> logger) : ITask, IToastAware
     {
         private const int MaxAttempts = 3;
+
         private static readonly IReadOnlyList<InventoryType> SourceInventoryTypes =
         [
             InventoryType.ArmoryMainHand,
@@ -55,22 +80,9 @@ internal static class EquipItem
             InventoryType.Inventory4,
         ];
 
-        private uint _itemId;
-        private Item _item = null!;
-        private List<ushort> _targetSlots = [];
         private int _attempts;
-
         private DateTime _continueAt = DateTime.MaxValue;
 
-        public ITask With(uint itemId)
-        {
-            _itemId = itemId;
-            _item = dataManager.GetExcelSheet<Item>()!.GetRow(itemId) ??
-                    throw new ArgumentOutOfRangeException(nameof(itemId));
-            _targetSlots = GetEquipSlot(_item) ?? throw new InvalidOperationException("Not a piece of equipment");
-            return this;
-        }
-
         public bool Start()
         {
             Equip();
@@ -87,10 +99,10 @@ internal static class EquipItem
             if (inventoryManager == null)
                 return ETaskResult.StillRunning;
 
-            foreach (ushort x in _targetSlots)
+            foreach (ushort x in targetSlots)
             {
                 var itemSlot = inventoryManager->GetInventorySlot(InventoryType.EquippedItems, x);
-                if (itemSlot != null && itemSlot->ItemId == _itemId)
+                if (itemSlot != null && itemSlot->ItemId == itemId)
                     return ETaskResult.TaskComplete;
             }
 
@@ -113,12 +125,12 @@ internal static class EquipItem
             if (equippedContainer == null)
                 return;
 
-            foreach (ushort slot in _targetSlots)
+            foreach (ushort slot in targetSlots)
             {
                 var itemSlot = equippedContainer->GetInventorySlot(slot);
-                if (itemSlot != null && itemSlot->ItemId == _itemId)
+                if (itemSlot != null && itemSlot->ItemId == itemId)
                 {
-                    logger.LogInformation("Already equipped {Item}, skipping step", _item.Name?.ToString());
+                    logger.LogInformation("Already equipped {Item}, skipping step", item.Name?.ToString());
                     return;
                 }
             }
@@ -129,24 +141,24 @@ internal static class EquipItem
                 if (sourceContainer == null)
                     continue;
 
-                if (inventoryManager->GetItemCountInContainer(_itemId, sourceInventoryType, true) == 0 &&
-                    inventoryManager->GetItemCountInContainer(_itemId, sourceInventoryType) == 0)
+                if (inventoryManager->GetItemCountInContainer(itemId, sourceInventoryType, true) == 0 &&
+                    inventoryManager->GetItemCountInContainer(itemId, sourceInventoryType) == 0)
                     continue;
 
                 for (ushort sourceSlot = 0; sourceSlot < sourceContainer->Size; sourceSlot++)
                 {
                     var sourceItem = sourceContainer->GetInventorySlot(sourceSlot);
-                    if (sourceItem == null || sourceItem->ItemId != _itemId)
+                    if (sourceItem == null || sourceItem->ItemId != itemId)
                         continue;
 
                     // Move the item to the first available slot
-                    ushort targetSlot = _targetSlots
+                    ushort targetSlot = targetSlots
                         .Where(x =>
                         {
                             var itemSlot = inventoryManager->GetInventorySlot(InventoryType.EquippedItems, x);
                             return itemSlot == null || itemSlot->ItemId == 0;
                         })
-                        .Concat(_targetSlots).First();
+                        .Concat(targetSlots).First();
 
                     logger.LogInformation(
                         "Equipping item from {SourceInventory}, {SourceSlot} to {TargetInventory}, {TargetSlot}",
@@ -160,19 +172,7 @@ internal static class EquipItem
             }
         }
 
-        private static List<ushort>? GetEquipSlot(Item item)
-        {
-            return item.EquipSlotCategory.Row switch
-            {
-                >= 1 and <= 11 => [(ushort)(item.EquipSlotCategory.Row - 1)],
-                12 => [11, 12], // rings
-                13 => [0],
-                17 => [13], // soul crystal
-                _ => null
-            };
-        }
-
-        public override string ToString() => $"Equip({_item.Name})";
+        public override string ToString() => $"Equip({item.Name})";
 
         public bool OnErrorToast(SeString message)
         {
index e94f819fa7076b5456da8d843c1e8fd46c984687..5ff7ab355a4488417b66d4f6d16d41cee9584934 100644 (file)
@@ -10,18 +10,23 @@ namespace Questionable.Controller.Steps.Interactions;
 
 internal static class EquipRecommended
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+    internal sealed class Factory(IClientState clientState, IChatGui chatGui) : SimpleTaskFactory
     {
         public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (step.InteractionType != EInteractionType.EquipRecommended)
                 return null;
 
-            return serviceProvider.GetRequiredService<DoEquipRecommended>();
+            return DoEquip();
+        }
+
+        public ITask DoEquip()
+        {
+            return new DoEquipRecommended(clientState, chatGui);
         }
     }
 
-    internal sealed class BeforeDutyOrInstance(IServiceProvider serviceProvider) : SimpleTaskFactory
+    internal sealed class BeforeDutyOrInstance(IClientState clientState, IChatGui chatGui) : SimpleTaskFactory
     {
         public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
@@ -30,11 +35,11 @@ internal static class EquipRecommended
                 step.InteractionType != EInteractionType.Combat)
                 return null;
 
-            return serviceProvider.GetRequiredService<DoEquipRecommended>();
+            return new DoEquipRecommended(clientState, chatGui);
         }
     }
 
-    internal sealed unsafe class DoEquipRecommended(IClientState clientState, IChatGui chatGui) : ITask
+    private sealed unsafe class DoEquipRecommended(IClientState clientState, IChatGui chatGui) : ITask
     {
         private bool _equipped;
 
index 50aae41a4db60fbf6a996db1a234452e9f337550..39caf1ae5de39ce5e41100df267316dafee4f8f3 100644 (file)
@@ -15,7 +15,7 @@ namespace Questionable.Controller.Steps.Interactions;
 
 internal static class Interact
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class Factory(GameFunctions gameFunctions, ICondition condition, ILoggerFactory loggerFactory) : ITaskFactory
     {
         public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
         {
@@ -36,41 +36,46 @@ internal static class Interact
 
             // if we're fast enough, it is possible to get the smalltalk prompt
             if (sequence.Sequence == 0 && sequence.Steps.IndexOf(step) == 0)
-                yield return serviceProvider.GetRequiredService<WaitAtEnd.WaitDelay>();
+                yield return new WaitAtEnd.WaitDelay();
 
-            yield return serviceProvider.GetRequiredService<DoInteract>()
-                .With(step.DataId.Value, quest, step.InteractionType,
+            yield return Interact(step.DataId.Value, quest, step.InteractionType,
                     step.TargetTerritoryId != null || quest.Id is SatisfactionSupplyNpcId);
         }
+
+        internal ITask Interact(uint dataId, Quest? quest, EInteractionType interactionType, bool skipMarkerCheck = false)
+        {
+            return new DoInteract(dataId, quest, interactionType, skipMarkerCheck, gameFunctions, condition,
+                loggerFactory.CreateLogger<DoInteract>());
+        }
     }
 
-    internal sealed class DoInteract(GameFunctions gameFunctions, ICondition condition, ILogger<DoInteract> logger)
+    internal sealed class DoInteract(
+        uint dataId,
+        Quest? quest,
+        EInteractionType interactionType,
+        bool skipMarkerCheck,
+        GameFunctions gameFunctions,
+        ICondition condition,
+        ILogger<DoInteract> logger)
         : ITask, IConditionChangeAware
     {
         private bool _needsUnmount;
         private EInteractionState _interactionState = EInteractionState.None;
         private DateTime _continueAt = DateTime.MinValue;
 
-        private uint DataId { get; set; }
-        public Quest? Quest { get; private set; }
-        public EInteractionType InteractionType { get; set; }
-        private bool SkipMarkerCheck { get; set; }
-
-        public DoInteract With(uint dataId, Quest? quest, EInteractionType interactionType, bool skipMarkerCheck)
+        public Quest? Quest => quest;
+        public EInteractionType InteractionType
         {
-            DataId = dataId;
-            Quest = quest;
-            InteractionType = interactionType;
-            SkipMarkerCheck = skipMarkerCheck;
-            return this;
+            get => interactionType;
+            set => interactionType = value;
         }
 
         public bool Start()
         {
-            IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId);
+            IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId);
             if (gameObject == null)
             {
-                logger.LogWarning("No game object with dataId {DataId}", DataId);
+                logger.LogWarning("No game object with dataId {DataId}", dataId);
                 return false;
             }
 
@@ -78,7 +83,7 @@ internal static class Interact
             if (!gameObject.IsTargetable && condition[ConditionFlag.Mounted] &&
                 gameObject.ObjectKind != ObjectKind.GatheringPoint)
             {
-                logger.LogInformation("Preparing interaction for {DataId} by unmounting", DataId);
+                logger.LogInformation("Preparing interaction for {DataId} by unmounting", dataId);
                 _needsUnmount = true;
                 gameFunctions.Unmount();
                 _continueAt = DateTime.Now.AddSeconds(1);
@@ -117,10 +122,10 @@ internal static class Interact
             if (_interactionState == EInteractionState.InteractionConfirmed)
                 return ETaskResult.TaskComplete;
 
-            if (InteractionType == EInteractionType.InternalGather && condition[ConditionFlag.Gathering])
+            if (interactionType == EInteractionType.InternalGather && condition[ConditionFlag.Gathering])
                 return ETaskResult.TaskComplete;
 
-            IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId);
+            IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId);
             if (gameObject == null || !gameObject.IsTargetable || !HasAnyMarker(gameObject))
                 return ETaskResult.StillRunning;
 
@@ -133,14 +138,14 @@ internal static class Interact
 
         private unsafe bool HasAnyMarker(IGameObject gameObject)
         {
-            if (SkipMarkerCheck || gameObject.ObjectKind != ObjectKind.EventNpc)
+            if (skipMarkerCheck || gameObject.ObjectKind != ObjectKind.EventNpc)
                 return true;
 
             var gameObjectStruct = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gameObject.Address;
             return gameObjectStruct->NamePlateIconId != 0;
         }
 
-        public override string ToString() => $"Interact({DataId})";
+        public override string ToString() => $"Interact({dataId})";
 
         public void OnConditionChange(ConditionFlag flag, bool value)
         {
index dd2dc92aafb93b5704426a0b5d35fbde9ee6fe2b..464eaaceff213d60c8134ce5be68c5129663bbde 100644 (file)
@@ -11,7 +11,11 @@ namespace Questionable.Controller.Steps.Interactions;
 
 internal static class Jump
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+    internal sealed class Factory(
+        MovementController movementController,
+        IClientState clientState,
+        IFramework framework,
+        ILoggerFactory loggerFactory) : SimpleTaskFactory
     {
         public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
@@ -21,43 +25,39 @@ internal static class Jump
             ArgumentNullException.ThrowIfNull(step.JumpDestination);
 
             if (step.JumpDestination.Type == EJumpType.SingleJump)
-            {
-                return serviceProvider.GetRequiredService<SingleJump>()
-                    .With(step.DataId, step.JumpDestination, step.Comment);
-            }
+                return SingleJump(step.DataId, step.JumpDestination, step.Comment);
             else
-            {
-                return serviceProvider.GetRequiredService<RepeatedJumps>()
-                    .With(step.DataId, step.JumpDestination, step.Comment);
-            }
+                return RepeatedJumps(step.DataId, step.JumpDestination, step.Comment);
+        }
+
+        public ITask SingleJump(uint? dataId, JumpDestination jumpDestination, string? comment)
+        {
+            return new DoSingleJump(dataId, jumpDestination, comment, movementController, clientState, framework);
+        }
+
+        public ITask RepeatedJumps(uint? dataId, JumpDestination jumpDestination, string? comment)
+        {
+            return new DoRepeatedJumps(dataId, jumpDestination, comment, movementController, clientState, framework,
+                loggerFactory.CreateLogger<DoRepeatedJumps>());
         }
     }
 
-    internal class SingleJump(
+    private class DoSingleJump(
+        uint? dataId,
+        JumpDestination jumpDestination,
+        string? comment,
         MovementController movementController,
         IClientState clientState,
         IFramework framework) : ITask
     {
-        public uint? DataId { get; set; }
-        public JumpDestination JumpDestination { get; set; } = null!;
-        public string? Comment { get; set; }
-
-        public ITask With(uint? dataId, JumpDestination jumpDestination, string? comment)
-        {
-            DataId = dataId;
-            JumpDestination = jumpDestination;
-            Comment = comment ?? string.Empty;
-            return this;
-        }
-
         public virtual bool Start()
         {
-            float stopDistance = JumpDestination.CalculateStopDistance();
-            if ((clientState.LocalPlayer!.Position - JumpDestination.Position).Length() <= stopDistance)
+            float stopDistance = jumpDestination.CalculateStopDistance();
+            if ((clientState.LocalPlayer!.Position - jumpDestination.Position).Length() <= stopDistance)
                 return false;
 
-            movementController.NavigateTo(EMovementType.Quest, DataId, [JumpDestination.Position], false, false,
-                JumpDestination.StopDistance ?? stopDistance);
+            movementController.NavigateTo(EMovementType.Quest, dataId, [jumpDestination.Position], false, false,
+                jumpDestination.StopDistance ?? stopDistance);
             framework.RunOnTick(() =>
                 {
                     unsafe
@@ -65,7 +65,7 @@ internal static class Jump
                         ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2);
                     }
                 },
-                TimeSpan.FromSeconds(JumpDestination.DelaySeconds ?? 0.5f));
+                TimeSpan.FromSeconds(jumpDestination.DelaySeconds ?? 0.5f));
             return true;
         }
 
@@ -81,22 +81,28 @@ internal static class Jump
             return ETaskResult.TaskComplete;
         }
 
-        public override string ToString() => $"Jump({Comment})";
+        public override string ToString() => $"Jump({comment})";
     }
 
-    internal sealed class RepeatedJumps(
+    private sealed class DoRepeatedJumps(
+        uint? dataId,
+        JumpDestination jumpDestination,
+        string? comment,
         MovementController movementController,
         IClientState clientState,
         IFramework framework,
-        ILogger<RepeatedJumps> logger) : SingleJump(movementController, clientState, framework)
+        ILogger<DoRepeatedJumps> logger)
+        : DoSingleJump(dataId, jumpDestination, comment, movementController, clientState, framework)
     {
+        private readonly JumpDestination _jumpDestination = jumpDestination;
+        private readonly string? _comment = comment;
         private readonly IClientState _clientState = clientState;
         private DateTime _continueAt = DateTime.MinValue;
         private int _attempts;
 
         public override bool Start()
         {
-            _continueAt = DateTime.Now + TimeSpan.FromSeconds(2 * (JumpDestination.DelaySeconds ?? 0.5f));
+            _continueAt = DateTime.Now + TimeSpan.FromSeconds(2 * (_jumpDestination.DelaySeconds ?? 0.5f));
             return base.Start();
         }
 
@@ -105,13 +111,13 @@ internal static class Jump
             if (DateTime.Now < _continueAt)
                 return ETaskResult.StillRunning;
 
-            float stopDistance = JumpDestination.CalculateStopDistance();
-            if ((_clientState.LocalPlayer!.Position - JumpDestination.Position).Length() <= stopDistance ||
-                _clientState.LocalPlayer.Position.Y >= JumpDestination.Position.Y - 0.5f)
+            float stopDistance = _jumpDestination.CalculateStopDistance();
+            if ((_clientState.LocalPlayer!.Position - _jumpDestination.Position).Length() <= stopDistance ||
+                _clientState.LocalPlayer.Position.Y >= _jumpDestination.Position.Y - 0.5f)
                 return ETaskResult.TaskComplete;
 
             logger.LogTrace("Y-Heights for jumps: player={A}, target={B}", _clientState.LocalPlayer.Position.Y,
-                JumpDestination.Position.Y - 0.5f);
+                _jumpDestination.Position.Y - 0.5f);
             unsafe
             {
                 ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2);
@@ -121,10 +127,10 @@ internal static class Jump
             if (_attempts >= 50)
                 throw new TaskException("Tried to jump too many times, didn't reach the target");
 
-            _continueAt = DateTime.Now + TimeSpan.FromSeconds(JumpDestination.DelaySeconds ?? 0.5f);
+            _continueAt = DateTime.Now + TimeSpan.FromSeconds(_jumpDestination.DelaySeconds ?? 0.5f);
             return ETaskResult.StillRunning;
         }
 
-        public override string ToString() => $"RepeatedJump({Comment})";
+        public override string ToString() => $"RepeatedJump({_comment})";
     }
 }
index f203d42ff3ec6d73474447806a33ac611ce985f3..7b8855a18c0a45eadc23f062639a25b7c92e35ca 100644 (file)
@@ -10,7 +10,10 @@ namespace Questionable.Controller.Steps.Interactions;
 
 internal static class Say
 {
-    internal sealed class Factory(IServiceProvider serviceProvider, ExcelFunctions excelFunctions) : ITaskFactory
+    internal sealed class Factory(
+        ChatFunctions chatFunctions,
+        Mount.Factory mountFactory,
+        ExcelFunctions excelFunctions) : ITaskFactory
     {
         public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
         {
@@ -21,31 +24,24 @@ internal static class Say
             ArgumentNullException.ThrowIfNull(step.ChatMessage);
 
             string? excelString =
-                excelFunctions.GetDialogueText(quest, step.ChatMessage.ExcelSheet, step.ChatMessage.Key, false).GetString();
+                excelFunctions.GetDialogueText(quest, step.ChatMessage.ExcelSheet, step.ChatMessage.Key, false)
+                    .GetString();
             ArgumentNullException.ThrowIfNull(excelString);
 
-            var unmount = serviceProvider.GetRequiredService<UnmountTask>();
-            var task = serviceProvider.GetRequiredService<UseChat>().With(excelString);
+            var unmount = mountFactory.Unmount();
+            var task = new UseChat(excelString, chatFunctions);
             return [unmount, task];
         }
     }
 
-    internal sealed class UseChat(ChatFunctions chatFunctions) : AbstractDelayedTask
+    private sealed class UseChat(string chatMessage, ChatFunctions chatFunctions) : AbstractDelayedTask
     {
-        public string ChatMessage { get; set; } = null!;
-
-        public ITask With(string chatMessage)
-        {
-            ChatMessage = chatMessage;
-            return this;
-        }
-
         protected override bool StartInternal()
         {
-            chatFunctions.ExecuteCommand($"/say {ChatMessage}");
+            chatFunctions.ExecuteCommand($"/say {chatMessage}");
             return true;
         }
 
-        public override string ToString() => $"Say({ChatMessage})";
+        public override string ToString() => $"Say({chatMessage})";
     }
 }
index 04e75c7876ba48d4234f4e634c76738a5fe516b6..2481e8f5c93279874a5a445bedf31481b9be00e5 100644 (file)
@@ -26,9 +26,17 @@ internal static class UseItem
     public const int VesperBayAetheryteTicket = 30362;
 
     internal sealed class Factory(
-        IServiceProvider serviceProvider,
+        Mount.Factory mountFactory,
+        MoveTo.Factory moveFactory,
+        Interact.Factory interactFactory,
+        AetheryteShortcut.Factory aetheryteShortcutFactory,
+        AethernetShortcut.Factory aethernetShortcutFactory,
+        GameFunctions gameFunctions,
+        QuestFunctions questFunctions,
+        ICondition condition,
         IClientState clientState,
         TerritoryData territoryData,
+        ILoggerFactory loggerFactory,
         ILogger<Factory> logger)
         : ITaskFactory
     {
@@ -48,8 +56,7 @@ internal static class UseItem
                         return CreateVesperBayFallbackTask();
                 }
 
-                var task = serviceProvider.GetRequiredService<Use>()
-                    .With(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
+                var task = OnSelf(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
 
                 int currentStepIndex = sequence.Steps.IndexOf(step);
                 QuestStep? nextStep = sequence.Steps.Skip(currentStepIndex + 1).FirstOrDefault();
@@ -59,47 +66,69 @@ internal static class UseItem
                     task,
                     new WaitConditionTask(() => clientState.TerritoryType == 140,
                         $"Wait(territory: {territoryData.GetNameAndId(140)})"),
-                    serviceProvider.GetRequiredService<MountTask>()
-                        .With(140,
-                            nextPosition != null ? MountTask.EMountIf.AwayFromPosition : MountTask.EMountIf.Always,
-                            nextPosition),
-                    serviceProvider.GetRequiredService<Move.MoveInternal>()
-                        .With(140, new(-408.92343f, 23.167036f, -351.16223f), 0.25f, dataId: null, disableNavMesh: true,
-                            sprint: false, fly: false)
+                    mountFactory.Mount(140,
+                        nextPosition != null ? Mount.EMountIf.AwayFromPosition : Mount.EMountIf.Always,
+                        nextPosition),
+                    moveFactory.Move(new MoveTo.MoveParams(140, new(-408.92343f, 23.167036f, -351.16223f), 0.25f,
+                        DataId: null, DisableNavMesh: true, Sprint: false, Fly: false))
                 ];
             }
 
-            var unmount = serviceProvider.GetRequiredService<UnmountTask>();
+            var unmount = mountFactory.Unmount();
             if (step.GroundTarget == true)
             {
                 ITask task;
                 if (step.DataId != null)
-                    task = serviceProvider.GetRequiredService<UseOnGround>()
-                        .With(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
+                    task = OnGroundTarget(quest.Id, step.DataId.Value, step.ItemId.Value,
+                        step.CompletionQuestVariablesFlags);
                 else
                 {
                     ArgumentNullException.ThrowIfNull(step.Position);
-                    task = serviceProvider.GetRequiredService<UseOnPosition>()
-                        .With(quest.Id, step.Position.Value, step.ItemId.Value,
-                            step.CompletionQuestVariablesFlags);
+                    task = OnPosition(quest.Id, step.Position.Value, step.ItemId.Value,
+                        step.CompletionQuestVariablesFlags);
                 }
 
                 return [unmount, task];
             }
             else if (step.DataId != null)
             {
-                var task = serviceProvider.GetRequiredService<UseOnObject>()
-                    .With(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
+                var task = OnObject(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
                 return [unmount, task];
             }
             else
             {
-                var task = serviceProvider.GetRequiredService<Use>()
-                    .With(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
+                var task = OnSelf(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
                 return [unmount, task];
             }
         }
 
+        public ITask OnGroundTarget(ElementId questId, uint dataId, uint itemId,
+            List<QuestWorkValue?> completionQuestVariablesFlags)
+        {
+            return new UseOnGround(questId, dataId, itemId, completionQuestVariablesFlags, gameFunctions,
+                questFunctions, condition, loggerFactory.CreateLogger<UseOnGround>());
+        }
+
+        public ITask OnPosition(ElementId questId, Vector3 position, uint itemId,
+            List<QuestWorkValue?> completionQuestVariablesFlags)
+        {
+            return new UseOnPosition(questId, position, itemId, completionQuestVariablesFlags, gameFunctions,
+                questFunctions, condition, loggerFactory.CreateLogger<UseOnPosition>());
+        }
+
+        public ITask OnObject(ElementId questId, uint dataId, uint itemId,
+            List<QuestWorkValue?> completionQuestVariablesFlags, bool startingCombat = false)
+        {
+            return new UseOnObject(questId, dataId, itemId, completionQuestVariablesFlags, startingCombat,
+                questFunctions, gameFunctions, condition, loggerFactory.CreateLogger<UseOnObject>());
+        }
+
+        public ITask OnSelf(ElementId questId, uint itemId, List<QuestWorkValue?> completionQuestVariablesFlags)
+        {
+            return new Use(questId, itemId, completionQuestVariablesFlags, gameFunctions, questFunctions, condition,
+                loggerFactory.CreateLogger<Use>());
+        }
+
         private IEnumerable<ITask> CreateVesperBayFallbackTask()
         {
             logger.LogWarning("No vesper bay aetheryte tickets in inventory, navigating via ferry in Limsa instead");
@@ -107,28 +136,32 @@ internal static class UseItem
             uint npcId = 1003540;
             ushort territoryId = 129;
             Vector3 destination = new(-360.9217f, 8f, 38.92566f);
-            yield return serviceProvider.GetRequiredService<AetheryteShortcut.UseAetheryteShortcut>()
-                .With(null, null, EAetheryteLocation.Limsa, territoryId);
-            yield return serviceProvider.GetRequiredService<AethernetShortcut.UseAethernetShortcut>()
-                .With(EAetheryteLocation.Limsa, EAetheryteLocation.LimsaArcanist);
-            yield return serviceProvider.GetRequiredService<WaitAtEnd.WaitDelay>();
-            yield return serviceProvider.GetRequiredService<Move.MoveInternal>()
-                .With(territoryId, destination, dataId: npcId, sprint: false);
-            yield return serviceProvider.GetRequiredService<Interact.DoInteract>()
-                .With(npcId, null, EInteractionType.None, true);
+            yield return aetheryteShortcutFactory.Use(null, null, EAetheryteLocation.Limsa, territoryId);
+            yield return aethernetShortcutFactory.Use(EAetheryteLocation.Limsa, EAetheryteLocation.LimsaArcanist);
+            yield return new WaitAtEnd.WaitDelay();
+            yield return
+                moveFactory.Move(new MoveTo.MoveParams(territoryId, destination, DataId: npcId, Sprint: false));
+            yield return interactFactory.Interact(npcId, null, EInteractionType.None, true);
         }
     }
 
-    internal abstract class UseItemBase(QuestFunctions questFunctions, ICondition condition, ILogger logger) : ITask
+    private abstract class UseItemBase(
+        ElementId? questId,
+        uint itemId,
+        IList<QuestWorkValue?> completionQuestVariablesFlags,
+        bool startingCombat,
+        QuestFunctions questFunctions,
+        ICondition condition,
+        ILogger logger) : ITask
     {
         private bool _usedItem;
         private DateTime _continueAt;
         private int _itemCount;
 
-        public ElementId? QuestId { get; set; }
-        public uint ItemId { get; set; }
-        public IList<QuestWorkValue?> CompletionQuestVariablesFlags { get; set; } = new List<QuestWorkValue?>();
-        public bool StartingCombat { get; set; }
+        public ElementId? QuestId => questId;
+        public uint ItemId => itemId;
+        public IList<QuestWorkValue?> CompletionQuestVariablesFlags => completionQuestVariablesFlags;
+        public bool StartingCombat => startingCombat;
 
         protected abstract bool UseItem();
 
@@ -149,9 +182,9 @@ internal static class UseItem
 
         public unsafe ETaskResult Update()
         {
-            if (QuestId is QuestId questId && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags))
+            if (QuestId is QuestId realQuestId && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags))
             {
-                QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(questId);
+                QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(realQuestId);
                 if (questWork != null &&
                     QuestWorkUtils.MatchesQuestWork(CompletionQuestVariablesFlags, questWork))
                     return ETaskResult.TaskComplete;
@@ -203,96 +236,66 @@ internal static class UseItem
     }
 
 
-    internal sealed class UseOnGround(
+    private sealed class UseOnGround(
+        ElementId? questId,
+        uint dataId,
+        uint itemId,
+        IList<QuestWorkValue?> completionQuestVariablesFlags,
         GameFunctions gameFunctions,
         QuestFunctions questFunctions,
         ICondition condition,
         ILogger<UseOnGround> logger)
-        : UseItemBase(questFunctions, condition, logger)
+        : UseItemBase(questId, itemId, completionQuestVariablesFlags, false, questFunctions, condition, logger)
     {
-        public uint DataId { get; set; }
-
-        public ITask With(ElementId? questId, uint dataId, uint itemId,
-            IList<QuestWorkValue?> completionQuestVariablesFlags)
-        {
-            QuestId = questId;
-            DataId = dataId;
-            ItemId = itemId;
-            CompletionQuestVariablesFlags = completionQuestVariablesFlags;
-            return this;
-        }
+        protected override bool UseItem() => gameFunctions.UseItemOnGround(dataId, ItemId);
 
-        protected override bool UseItem() => gameFunctions.UseItemOnGround(DataId, ItemId);
-
-        public override string ToString() => $"UseItem({ItemId} on ground at {DataId})";
+        public override string ToString() => $"UseItem({ItemId} on ground at {dataId})";
     }
 
-    internal sealed class UseOnPosition(
+    private sealed class UseOnPosition(
+        ElementId? questId,
+        Vector3 position,
+        uint itemId,
+        IList<QuestWorkValue?> completionQuestVariablesFlags,
         GameFunctions gameFunctions,
         QuestFunctions questFunctions,
         ICondition condition,
         ILogger<UseOnPosition> logger)
-        : UseItemBase(questFunctions, condition, logger)
+        : UseItemBase(questId, itemId, completionQuestVariablesFlags, false, questFunctions, condition, logger)
     {
-        public Vector3 Position { get; set; }
-
-        public ITask With(ElementId? questId, Vector3 position, uint itemId,
-            IList<QuestWorkValue?> completionQuestVariablesFlags)
-        {
-            QuestId = questId;
-            Position = position;
-            ItemId = itemId;
-            CompletionQuestVariablesFlags = completionQuestVariablesFlags;
-            return this;
-        }
-
-        protected override bool UseItem() => gameFunctions.UseItemOnPosition(Position, ItemId);
+        protected override bool UseItem() => gameFunctions.UseItemOnPosition(position, ItemId);
 
         public override string ToString() =>
-            $"UseItem({ItemId} on ground at {Position.ToString("G", CultureInfo.InvariantCulture)})";
+            $"UseItem({ItemId} on ground at {position.ToString("G", CultureInfo.InvariantCulture)})";
     }
 
-    internal sealed class UseOnObject(
+    private sealed class UseOnObject(
+        ElementId? questId,
+        uint dataId,
+        uint itemId,
+        IList<QuestWorkValue?> completionQuestVariablesFlags,
+        bool startingCombat,
         QuestFunctions questFunctions,
         GameFunctions gameFunctions,
         ICondition condition,
         ILogger<UseOnObject> logger)
-        : UseItemBase(questFunctions, condition, logger)
+        : UseItemBase(questId, itemId, completionQuestVariablesFlags, startingCombat, questFunctions, condition, logger)
     {
-        public uint DataId { get; set; }
-
-        public ITask With(ElementId? questId, uint dataId, uint itemId,
-            IList<QuestWorkValue?> completionQuestVariablesFlags,
-            bool startingCombat = false)
-        {
-            QuestId = questId;
-            DataId = dataId;
-            ItemId = itemId;
-            StartingCombat = startingCombat;
-            CompletionQuestVariablesFlags = completionQuestVariablesFlags;
-            return this;
-        }
+        protected override bool UseItem() => gameFunctions.UseItem(dataId, ItemId);
 
-        protected override bool UseItem() => gameFunctions.UseItem(DataId, ItemId);
-
-        public override string ToString() => $"UseItem({ItemId} on {DataId})";
+        public override string ToString() => $"UseItem({ItemId} on {dataId})";
     }
 
-    internal sealed class Use(
+    private sealed class Use(
+        ElementId? questId,
+        uint itemId,
+        IList<QuestWorkValue?> completionQuestVariablesFlags,
         GameFunctions gameFunctions,
         QuestFunctions questFunctions,
         ICondition condition,
         ILogger<Use> logger)
-        : UseItemBase(questFunctions, condition, logger)
+        : UseItemBase(questId, itemId, completionQuestVariablesFlags, false, questFunctions, condition, logger)
     {
-        public ITask With(ElementId? questId, uint itemId, IList<QuestWorkValue?> completionQuestVariablesFlags)
-        {
-            QuestId = questId;
-            ItemId = itemId;
-            CompletionQuestVariablesFlags = completionQuestVariablesFlags;
-            return this;
-        }
-
         protected override bool UseItem() => gameFunctions.UseItem(ItemId);
 
         public override string ToString() => $"UseItem({ItemId})";
index 545324a422118ccd11896eb4352ec3df149667a3..7b12c95e130253a0a13ab89f447bae8934e60142 100644 (file)
@@ -9,6 +9,7 @@ using FFXIVClientStructs.FFXIV.Component.GUI;
 using LLib.GameUI;
 using Microsoft.Extensions.DependencyInjection;
 using Questionable.Controller.Steps.Common;
+using Questionable.Functions;
 using Questionable.Model;
 using Questionable.Model.Questing;
 using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
@@ -17,31 +18,23 @@ namespace Questionable.Controller.Steps.Leves;
 
 internal static class InitiateLeve
 {
-    internal sealed class Factory(IServiceProvider serviceProvider, ICondition condition) : ITaskFactory
+    internal sealed class Factory(IGameGui gameGui, ICondition condition) : ITaskFactory
     {
         public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (step.InteractionType != EInteractionType.InitiateLeve)
                 yield break;
 
-            yield return serviceProvider.GetRequiredService<SkipInitiateIfActive>().With(quest.Id);
-            yield return serviceProvider.GetRequiredService<OpenJournal>().With(quest.Id);
-            yield return serviceProvider.GetRequiredService<Initiate>().With(quest.Id);
-            yield return serviceProvider.GetRequiredService<SelectDifficulty>();
+            yield return new SkipInitiateIfActive(quest.Id);
+            yield return new OpenJournal(quest.Id);
+            yield return new Initiate(quest.Id, gameGui);
+            yield return new SelectDifficulty(gameGui);
             yield return new WaitConditionTask(() => condition[ConditionFlag.BoundByDuty], "Wait(BoundByDuty)");
         }
     }
 
-    internal sealed unsafe class SkipInitiateIfActive : ITask
+    internal sealed unsafe class SkipInitiateIfActive(ElementId elementId) : ITask
     {
-        private ElementId _elementId = null!;
-
-        public ITask With(ElementId elementId)
-        {
-            _elementId = elementId;
-            return this;
-        }
-
         public bool Start() => true;
 
         public ETaskResult Update()
@@ -50,31 +43,23 @@ internal static class InitiateLeve
             if (director != null &&
                 director->EventHandlerInfo != null &&
                 director->EventHandlerInfo->EventId.ContentId == EventHandlerType.GatheringLeveDirector &&
-                director->ContentId == _elementId.Value)
+                director->ContentId == elementId.Value)
                 return ETaskResult.SkipRemainingTasksForStep;
 
             return ETaskResult.TaskComplete;
         }
 
-        public override string ToString() => $"CheckIfAlreadyActive({_elementId})";
+        public override string ToString() => $"CheckIfAlreadyActive({elementId})";
     }
 
-    internal sealed unsafe class OpenJournal : ITask
+    internal sealed unsafe class OpenJournal(ElementId elementId) : ITask
     {
-        private ElementId _elementId = null!;
-        private uint _questType;
+        private readonly uint _questType = elementId is LeveId ? 2u : 1u;
         private DateTime _openedAt = DateTime.MinValue;
 
-        public ITask With(ElementId elementId)
-        {
-            _elementId = elementId;
-            _questType = _elementId is LeveId ? 2u : 1u;
-            return this;
-        }
-
         public bool Start()
         {
-            AgentQuestJournal.Instance()->OpenForQuest(_elementId.Value, _questType);
+            AgentQuestJournal.Instance()->OpenForQuest(elementId.Value, _questType);
             _openedAt = DateTime.Now;
             return true;
         }
@@ -83,32 +68,24 @@ internal static class InitiateLeve
         {
             AgentQuestJournal* agentQuestJournal = AgentQuestJournal.Instance();
             if (agentQuestJournal->IsAgentActive() &&
-                agentQuestJournal->SelectedQuestId == _elementId.Value &&
+                agentQuestJournal->SelectedQuestId == elementId.Value &&
                 agentQuestJournal->SelectedQuestType == _questType)
                 return ETaskResult.TaskComplete;
 
             if (DateTime.Now > _openedAt.AddSeconds(3))
             {
-                AgentQuestJournal.Instance()->OpenForQuest(_elementId.Value, _questType);
+                AgentQuestJournal.Instance()->OpenForQuest(elementId.Value, _questType);
                 _openedAt = DateTime.Now;
             }
 
             return ETaskResult.StillRunning;
         }
 
-        public override string ToString() => $"OpenJournal({_elementId})";
+        public override string ToString() => $"OpenJournal({elementId})";
     }
 
-    internal sealed unsafe class Initiate(IGameGui gameGui) : ITask
+    internal sealed unsafe class Initiate(ElementId elementId, IGameGui gameGui) : ITask
     {
-        private ElementId _elementId = null!;
-
-        public ITask With(ElementId elementId)
-        {
-            _elementId = elementId;
-            return this;
-        }
-
         public bool Start() => true;
 
         public ETaskResult Update()
@@ -118,7 +95,7 @@ internal static class InitiateLeve
                 var pickQuest = stackalloc AtkValue[]
                 {
                     new() { Type = ValueType.Int, Int = 4 },
-                    new() { Type = ValueType.UInt, Int = _elementId.Value }
+                    new() { Type = ValueType.UInt, Int = elementId.Value }
                 };
                 addonJournalDetail->FireCallback(2, pickQuest);
                 return ETaskResult.TaskComplete;
@@ -127,7 +104,7 @@ internal static class InitiateLeve
             return ETaskResult.StillRunning;
         }
 
-        public override string ToString() => $"InitiateLeve({_elementId})";
+        public override string ToString() => $"InitiateLeve({elementId})";
     }
 
     internal sealed unsafe class SelectDifficulty(IGameGui gameGui) : ITask
index cca44d09b39077d762880eade44d67889a57e733..9297297e763c8457d7d17739fcc8a7ea4108cb6e 100644 (file)
@@ -20,7 +20,16 @@ namespace Questionable.Controller.Steps.Shared;
 
 internal static class AethernetShortcut
 {
-    internal sealed class Factory(IServiceProvider serviceProvider, MovementController movementController)
+    internal sealed class Factory(
+        MovementController movementController,
+        AetheryteFunctions aetheryteFunctions,
+        GameFunctions gameFunctions,
+        IClientState clientState,
+        AetheryteData aetheryteData,
+        TerritoryData territoryData,
+        LifestreamIpc lifestreamIpc,
+        ICondition condition,
+        ILoggerFactory loggerFactory)
         : ITaskFactory
     {
         public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
@@ -30,12 +39,22 @@ internal static class AethernetShortcut
 
             yield return new WaitConditionTask(() => movementController.IsNavmeshReady,
                 "Wait(navmesh ready)");
-            yield return serviceProvider.GetRequiredService<UseAethernetShortcut>()
-                .With(step.AethernetShortcut.From, step.AethernetShortcut.To, step.SkipConditions?.AethernetShortcutIf);
+            yield return Use(step.AethernetShortcut.From, step.AethernetShortcut.To,
+                step.SkipConditions?.AethernetShortcutIf);
+        }
+
+        public ITask Use(EAetheryteLocation from, EAetheryteLocation to, SkipAetheryteCondition? skipConditions = null)
+        {
+            return new UseAethernetShortcut(from, to, skipConditions ?? new(),
+                loggerFactory.CreateLogger<UseAethernetShortcut>(), aetheryteFunctions, gameFunctions, clientState,
+                aetheryteData, territoryData, lifestreamIpc, movementController, condition);
         }
     }
 
     internal sealed class UseAethernetShortcut(
+        EAetheryteLocation from,
+        EAetheryteLocation to,
+        SkipAetheryteCondition skipConditions,
         ILogger<UseAethernetShortcut> logger,
         AetheryteFunctions aetheryteFunctions,
         GameFunctions gameFunctions,
@@ -51,68 +70,58 @@ internal static class AethernetShortcut
         private bool _triedMounting;
         private DateTime _continueAt = DateTime.MinValue;
 
-        public EAetheryteLocation From { get; set; }
-        public EAetheryteLocation To { get; set; }
-        public SkipAetheryteCondition SkipConditions { get; set; } = null!;
-
-        public ITask With(EAetheryteLocation from, EAetheryteLocation to,
-            SkipAetheryteCondition? skipConditions = null)
-        {
-            From = from;
-            To = to;
-            SkipConditions = skipConditions ?? new();
-            return this;
-        }
+        public EAetheryteLocation From => from;
+        public EAetheryteLocation To => to;
 
         public bool Start()
         {
-            if (!SkipConditions.Never)
+            if (!skipConditions.Never)
             {
-                if (SkipConditions.InSameTerritory && clientState.TerritoryType == aetheryteData.TerritoryIds[To])
+                if (skipConditions.InSameTerritory && clientState.TerritoryType == aetheryteData.TerritoryIds[to])
                 {
                     logger.LogInformation("Skipping aethernet shortcut because the target is in the same territory");
                     return false;
                 }
 
-                if (SkipConditions.InTerritory.Contains(clientState.TerritoryType))
+                if (skipConditions.InTerritory.Contains(clientState.TerritoryType))
                 {
                     logger.LogInformation(
                         "Skipping aethernet shortcut because the target is in the specified territory");
                     return false;
                 }
 
-                if (SkipConditions.AetheryteLocked != null &&
-                    !aetheryteFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteLocked.Value))
+                if (skipConditions.AetheryteLocked != null &&
+                    !aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteLocked.Value))
                 {
                     logger.LogInformation("Skipping aethernet shortcut because the target aetheryte is locked");
                     return false;
                 }
 
-                if (SkipConditions.AetheryteUnlocked != null &&
-                    aetheryteFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteUnlocked.Value))
+                if (skipConditions.AetheryteUnlocked != null &&
+                    aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteUnlocked.Value))
                 {
                     logger.LogInformation("Skipping aethernet shortcut because the target aetheryte is unlocked");
                     return false;
                 }
             }
 
-            if (aetheryteFunctions.IsAetheryteUnlocked(From) &&
-                aetheryteFunctions.IsAetheryteUnlocked(To))
+            if (aetheryteFunctions.IsAetheryteUnlocked(from) &&
+                aetheryteFunctions.IsAetheryteUnlocked(to))
             {
                 ushort territoryType = clientState.TerritoryType;
                 Vector3 playerPosition = clientState.LocalPlayer!.Position;
 
                 // closer to the source
-                if (aetheryteData.CalculateDistance(playerPosition, territoryType, From) <
-                    aetheryteData.CalculateDistance(playerPosition, territoryType, To))
+                if (aetheryteData.CalculateDistance(playerPosition, territoryType, from) <
+                    aetheryteData.CalculateDistance(playerPosition, territoryType, to))
                 {
-                    if (aetheryteData.CalculateDistance(playerPosition, territoryType, From) <
-                        (From.IsFirmamentAetheryte() ? 11f : 4f))
+                    if (aetheryteData.CalculateDistance(playerPosition, territoryType, from) <
+                        (from.IsFirmamentAetheryte() ? 11f : 4f))
                     {
                         DoTeleport();
                         return true;
                     }
-                    else if (From == EAetheryteLocation.SolutionNine)
+                    else if (from == EAetheryteLocation.SolutionNine)
                     {
                         logger.LogInformation("Moving to S9 aetheryte");
                         List<Vector3> nearbyPoints =
@@ -125,14 +134,14 @@ internal static class AethernetShortcut
 
                         Vector3 closestPoint = nearbyPoints.MinBy(x => (playerPosition - x).Length());
                         _moving = true;
-                        movementController.NavigateTo(EMovementType.Quest, (uint)From, closestPoint, false, true,
+                        movementController.NavigateTo(EMovementType.Quest, (uint)from, closestPoint, false, true,
                             0.25f);
                         return true;
                     }
                     else
                     {
                         if (territoryData.CanUseMount(territoryType) &&
-                            aetheryteData.CalculateDistance(playerPosition, territoryType, From) > 30 &&
+                            aetheryteData.CalculateDistance(playerPosition, territoryType, from) > 30 &&
                             !gameFunctions.HasStatusPreventingMount())
                         {
                             _triedMounting = gameFunctions.Mount();
@@ -151,7 +160,7 @@ internal static class AethernetShortcut
             else
                 logger.LogWarning(
                     "Aethernet shortcut not unlocked (from: {FromAetheryte}, to: {ToAetheryte}), walking manually",
-                    From, To);
+                    from, to);
 
             return false;
         }
@@ -160,26 +169,26 @@ internal static class AethernetShortcut
         {
             logger.LogInformation("Moving to aethernet shortcut");
             _moving = true;
-            movementController.NavigateTo(EMovementType.Quest, (uint)From, aetheryteData.Locations[From],
+            movementController.NavigateTo(EMovementType.Quest, (uint)from, aetheryteData.Locations[from],
                 false, true,
-                From.IsFirmamentAetheryte()
+                from.IsFirmamentAetheryte()
                     ? 4.4f
-                    : AetheryteConverter.IsLargeAetheryte(From)
+                    : AetheryteConverter.IsLargeAetheryte(from)
                         ? 10.9f
                         : 6.9f);
         }
 
         private void DoTeleport()
         {
-            if (From.IsFirmamentAetheryte())
+            if (from.IsFirmamentAetheryte())
             {
                 logger.LogInformation("Using manual teleport interaction");
-                _teleported = gameFunctions.InteractWith((uint)From, ObjectKind.EventObj);
+                _teleported = gameFunctions.InteractWith((uint)from, ObjectKind.EventObj);
             }
             else
             {
-                logger.LogInformation("Using lifestream to teleport to {Destination}", To);
-                lifestreamIpc.Teleport(To);
+                logger.LogInformation("Using lifestream to teleport to {Destination}", to);
+                lifestreamIpc.Teleport(to);
                 _teleported = true;
             }
         }
@@ -219,22 +228,22 @@ internal static class AethernetShortcut
                 return ETaskResult.StillRunning;
             }
 
-            if (aetheryteData.IsAirshipLanding(To))
+            if (aetheryteData.IsAirshipLanding(to))
             {
                 if (aetheryteData.CalculateAirshipLandingDistance(clientState.LocalPlayer?.Position ?? Vector3.Zero,
-                        clientState.TerritoryType, To) > 5)
+                        clientState.TerritoryType, to) > 5)
                     return ETaskResult.StillRunning;
             }
-            else if (aetheryteData.IsCityAetheryte(To))
+            else if (aetheryteData.IsCityAetheryte(to))
             {
                 if (aetheryteData.CalculateDistance(clientState.LocalPlayer?.Position ?? Vector3.Zero,
-                        clientState.TerritoryType, To) > 20)
+                        clientState.TerritoryType, to) > 20)
                     return ETaskResult.StillRunning;
             }
             else
             {
                 // some overworld location (e.g. 'Tesselation (Lakeland)' would end up here
-                if (clientState.TerritoryType != aetheryteData.TerritoryIds[To])
+                if (clientState.TerritoryType != aetheryteData.TerritoryIds[to])
                     return ETaskResult.StillRunning;
             }
 
@@ -242,6 +251,6 @@ internal static class AethernetShortcut
             return ETaskResult.TaskComplete;
         }
 
-        public override string ToString() => $"UseAethernet({From} -> {To})";
+        public override string ToString() => $"UseAethernet({from} -> {to})";
     }
 }
index 52bb26b69462ec653092dff4e29c6309519f8418..2d72afd1a6be0c89c8150703194a6d8985eb874e 100644 (file)
@@ -18,23 +18,38 @@ namespace Questionable.Controller.Steps.Shared;
 internal static class AetheryteShortcut
 {
     internal sealed class Factory(
-        IServiceProvider serviceProvider,
-        AetheryteData aetheryteData) : ITaskFactory
+        AetheryteData aetheryteData,
+        AetheryteFunctions aetheryteFunctions,
+        QuestFunctions questFunctions,
+        IClientState clientState,
+        IChatGui chatGui,
+        ILoggerFactory loggerFactory) : ITaskFactory
     {
         public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (step.AetheryteShortcut == null)
                 yield break;
 
-            yield return serviceProvider.GetRequiredService<UseAetheryteShortcut>()
-                .With(step, quest.Id, step.AetheryteShortcut.Value,
-                    aetheryteData.TerritoryIds[step.AetheryteShortcut.Value]);
-            yield return serviceProvider.GetRequiredService<WaitAtEnd.WaitDelay>()
-                .With(TimeSpan.FromSeconds(0.5));
+            yield return Use(step, quest.Id, step.AetheryteShortcut.Value,
+                aetheryteData.TerritoryIds[step.AetheryteShortcut.Value]);
+            yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(0.5));
+        }
+
+        public ITask Use(QuestStep? step, ElementId? elementId, EAetheryteLocation targetAetheryte,
+            ushort expectedTerritoryId)
+        {
+            return new UseAetheryteShortcut(step, elementId, targetAetheryte, expectedTerritoryId,
+                loggerFactory.CreateLogger<UseAetheryteShortcut>(), aetheryteFunctions, questFunctions, clientState,
+                chatGui, aetheryteData);
         }
     }
 
-    internal sealed class UseAetheryteShortcut(
+    /// <param name="expectedTerritoryId">If using an aethernet shortcut after, the aetheryte's territory-id and the step's territory-id can differ, we always use the aetheryte's territory-id.</param>
+    private sealed class UseAetheryteShortcut(
+        QuestStep? step,
+        ElementId? elementId,
+        EAetheryteLocation targetAetheryte,
+        ushort expectedTerritoryId,
         ILogger<UseAetheryteShortcut> logger,
         AetheryteFunctions aetheryteFunctions,
         QuestFunctions questFunctions,
@@ -45,26 +60,6 @@ internal static class AetheryteShortcut
         private bool _teleported;
         private DateTime _continueAt;
 
-        public QuestStep? Step { get; set; }
-        public ElementId? ElementId { get; set; }
-        public EAetheryteLocation TargetAetheryte { get; set; }
-
-        /// <summary>
-        /// If using an aethernet shortcut after, the aetheryte's territory-id and the step's territory-id can differ,
-        /// we always use the aetheryte's territory-id.
-        /// </summary>
-        public ushort ExpectedTerritoryId { get; set; }
-
-        public ITask With(QuestStep? step, ElementId? elementId, EAetheryteLocation targetAetheryte,
-            ushort expectedTerritoryId)
-        {
-            Step = step;
-            ElementId = elementId;
-            TargetAetheryte = targetAetheryte;
-            ExpectedTerritoryId = expectedTerritoryId;
-            return this;
-        }
-
         public bool Start() => !ShouldSkipTeleport();
 
         public ETaskResult Update()
@@ -78,7 +73,7 @@ internal static class AetheryteShortcut
                 return ETaskResult.StillRunning;
             }
 
-            if (clientState.TerritoryType == ExpectedTerritoryId)
+            if (clientState.TerritoryType == expectedTerritoryId)
                 return ETaskResult.TaskComplete;
 
             return ETaskResult.StillRunning;
@@ -87,9 +82,9 @@ internal static class AetheryteShortcut
         private bool ShouldSkipTeleport()
         {
             ushort territoryType = clientState.TerritoryType;
-            if (Step != null)
+            if (step != null)
             {
-                var skipConditions = Step.SkipConditions?.AetheryteShortcutIf ?? new();
+                var skipConditions = step.SkipConditions?.AetheryteShortcutIf ?? new();
                 if (skipConditions is { Never: false })
                 {
                     if (skipConditions.InTerritory.Contains(territoryType))
@@ -112,12 +107,12 @@ internal static class AetheryteShortcut
                         return true;
                     }
 
-                    if (ElementId != null)
+                    if (elementId != null)
                     {
-                        QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(ElementId);
+                        QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(elementId);
                         if (skipConditions.RequiredQuestVariablesNotMet &&
                             questWork != null &&
-                            !QuestWorkUtils.MatchesRequiredQuestWorkConfig(Step.RequiredQuestVariables, questWork,
+                            !QuestWorkUtils.MatchesRequiredQuestWorkConfig(step.RequiredQuestVariables, questWork,
                                 logger))
                         {
                             logger.LogInformation("Skipping aetheryte teleport, as required variables do not match");
@@ -126,10 +121,11 @@ internal static class AetheryteShortcut
                     }
 
 
-
-                    if (skipConditions.NearPosition is { } nearPosition && clientState.TerritoryType == Step.TerritoryId)
+                    if (skipConditions.NearPosition is { } nearPosition &&
+                        clientState.TerritoryType == step.TerritoryId)
                     {
-                        if (Vector3.Distance(nearPosition.Position, clientState.LocalPlayer!.Position) <= nearPosition.MaximumDistance)
+                        if (Vector3.Distance(nearPosition.Position, clientState.LocalPlayer!.Position) <=
+                            nearPosition.MaximumDistance)
                         {
                             logger.LogInformation("Skipping aetheryte shortcut, as we're near the position");
                             return true;
@@ -137,7 +133,7 @@ internal static class AetheryteShortcut
                     }
                 }
 
-                if (ExpectedTerritoryId == territoryType)
+                if (expectedTerritoryId == territoryType)
                 {
                     if (!skipConditions.Never)
                     {
@@ -148,17 +144,17 @@ internal static class AetheryteShortcut
                         }
 
                         Vector3 pos = clientState.LocalPlayer!.Position;
-                        if (Step.Position != null &&
-                            (pos - Step.Position.Value).Length() < Step.CalculateActualStopDistance())
+                        if (step.Position != null &&
+                            (pos - step.Position.Value).Length() < step.CalculateActualStopDistance())
                         {
                             logger.LogInformation("Skipping aetheryte teleport, we're near the target");
                             return true;
                         }
 
-                        if (aetheryteData.CalculateDistance(pos, territoryType, TargetAetheryte) < 20 ||
-                            (Step.AethernetShortcut != null &&
-                             (aetheryteData.CalculateDistance(pos, territoryType, Step.AethernetShortcut.From) < 20 ||
-                              aetheryteData.CalculateDistance(pos, territoryType, Step.AethernetShortcut.To) < 20)))
+                        if (aetheryteData.CalculateDistance(pos, territoryType, targetAetheryte) < 20 ||
+                            (step.AethernetShortcut != null &&
+                             (aetheryteData.CalculateDistance(pos, territoryType, step.AethernetShortcut.From) < 20 ||
+                              aetheryteData.CalculateDistance(pos, territoryType, step.AethernetShortcut.To) < 20)))
                         {
                             logger.LogInformation("Skipping aetheryte teleport");
                             return true;
@@ -172,7 +168,7 @@ internal static class AetheryteShortcut
 
         private bool DoTeleport()
         {
-            if (!aetheryteFunctions.CanTeleport(TargetAetheryte))
+            if (!aetheryteFunctions.CanTeleport(targetAetheryte))
             {
                 if (!aetheryteFunctions.IsTeleportUnlocked())
                     throw new TaskException("Teleport is not unlocked, attune to any aetheryte first.");
@@ -184,12 +180,12 @@ internal static class AetheryteShortcut
 
             _continueAt = DateTime.Now.AddSeconds(8);
 
-            if (!aetheryteFunctions.IsAetheryteUnlocked(TargetAetheryte))
+            if (!aetheryteFunctions.IsAetheryteUnlocked(targetAetheryte))
             {
-                chatGui.PrintError($"[Questionable] Aetheryte {TargetAetheryte} is not unlocked.");
+                chatGui.PrintError($"[Questionable] Aetheryte {targetAetheryte} is not unlocked.");
                 throw new TaskException("Aetheryte is not unlocked");
             }
-            else if (aetheryteFunctions.TeleportAetheryte(TargetAetheryte))
+            else if (aetheryteFunctions.TeleportAetheryte(targetAetheryte))
             {
                 logger.LogInformation("Travelling via aetheryte...");
                 return true;
@@ -201,6 +197,6 @@ internal static class AetheryteShortcut
             }
         }
 
-        public override string ToString() => $"UseAetheryte({TargetAetheryte})";
+        public override string ToString() => $"UseAetheryte({targetAetheryte})";
     }
 }
index 92487f6747ca01874a1bb9c32bb5c6254ab6ed6a..5d09f37c522ed49b01be3b908cb471862b2abf4e 100644 (file)
@@ -7,18 +7,22 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
 using FFXIVClientStructs.FFXIV.Component.GUI;
 using LLib.GameData;
 using Lumina.Excel.GeneratedSheets;
-using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
-using Questionable.Controller.Steps.Common;
 using Questionable.External;
 using Questionable.Model.Questing;
+using Mount = Questionable.Controller.Steps.Common.Mount;
 using Quest = Questionable.Model.Quest;
 
 namespace Questionable.Controller.Steps.Shared;
 
 internal static class Craft
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class Factory(
+        IDataManager dataManager,
+        IClientState clientState,
+        ArtisanIpc artisanIpc,
+        Mount.Factory mountFactory,
+        ILoggerFactory loggerFactory) : ITaskFactory
     {
         public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
         {
@@ -29,40 +33,34 @@ internal static class Craft
             ArgumentNullException.ThrowIfNull(step.ItemCount);
             return
             [
-                serviceProvider.GetRequiredService<UnmountTask>(),
-                serviceProvider.GetRequiredService<DoCraft>()
-                    .With(step.ItemId.Value, step.ItemCount.Value)
+                mountFactory.Unmount(),
+                Craft(step.ItemId.Value, step.ItemCount.Value)
             ];
         }
+
+        public ITask Craft(uint itemId, int itemCount) =>
+            new DoCraft(itemId, itemCount, dataManager, clientState, artisanIpc, loggerFactory.CreateLogger<DoCraft>());
     }
 
-    internal sealed class DoCraft(
+    private sealed class DoCraft(
+        uint itemId,
+        int itemCount,
         IDataManager dataManager,
         IClientState clientState,
         ArtisanIpc artisanIpc,
         ILogger<DoCraft> logger) : ITask
     {
-        private uint _itemId;
-        private int _itemCount;
-
-        public ITask With(uint itemId, int itemCount)
-        {
-            _itemId = itemId;
-            _itemCount = itemCount;
-            return this;
-        }
-
         public bool Start()
         {
             if (HasRequestedItems())
             {
-                logger.LogInformation("Already own {ItemCount}x {ItemId}", _itemCount, _itemId);
+                logger.LogInformation("Already own {ItemCount}x {ItemId}", itemCount, itemId);
                 return false;
             }
 
-            RecipeLookup? recipeLookup = dataManager.GetExcelSheet<RecipeLookup>()!.GetRow(_itemId);
+            RecipeLookup? recipeLookup = dataManager.GetExcelSheet<RecipeLookup>()!.GetRow(itemId);
             if (recipeLookup == null)
-                throw new TaskException($"Item {_itemId} is not craftable");
+                throw new TaskException($"Item {itemId} is not craftable");
 
             uint recipeId = (EClassJob)clientState.LocalPlayer!.ClassJob.Id switch
             {
@@ -94,12 +92,12 @@ internal static class Craft
             }
 
             if (recipeId == 0)
-                throw new TaskException($"Unable to determine recipe for item {_itemId}");
+                throw new TaskException($"Unable to determine recipe for item {itemId}");
 
-            int remainingItemCount = _itemCount - GetOwnedItemCount();
+            int remainingItemCount = itemCount - GetOwnedItemCount();
             logger.LogInformation(
                 "Starting craft for item {ItemId} with recipe {RecipeId} for {RemainingItemCount} items",
-                _itemId, recipeId, remainingItemCount);
+                itemId, recipeId, remainingItemCount);
             if (!artisanIpc.CraftItem((ushort)recipeId, remainingItemCount))
                 throw new TaskException($"Failed to start Artisan craft for recipe {recipeId}");
 
@@ -130,15 +128,15 @@ internal static class Craft
             return ETaskResult.StillRunning;
         }
 
-        private bool HasRequestedItems() => GetOwnedItemCount() >= _itemCount;
+        private bool HasRequestedItems() => GetOwnedItemCount() >= itemCount;
 
         private unsafe int GetOwnedItemCount()
         {
             InventoryManager* inventoryManager = InventoryManager.Instance();
-            return inventoryManager->GetInventoryItemCount(_itemId, isHq: false, checkEquipped: false)
-                   + inventoryManager->GetInventoryItemCount(_itemId, isHq: true, checkEquipped: false);
+            return inventoryManager->GetInventoryItemCount(itemId, isHq: false, checkEquipped: false)
+                   + inventoryManager->GetInventoryItemCount(itemId, isHq: true, checkEquipped: false);
         }
 
-        public override string ToString() => $"Craft {_itemCount}x {_itemId} (with Artisan)";
+        public override string ToString() => $"Craft {itemCount}x {itemId} (with Artisan)";
     }
 }
index 9318ce13c8b9bcd7c51199129bc7f65086f12db7..6e821e6a0af044b1601693c38b89832ab8f775ea 100644 (file)
@@ -10,7 +10,6 @@ using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Questionable.Controller.Steps.Common;
 using Questionable.Data;
-using Questionable.Functions;
 using Questionable.Model;
 using Questionable.Model.Gathering;
 using Questionable.Model.Questing;
@@ -22,6 +21,7 @@ internal static class GatheringRequiredItems
     internal sealed class Factory(
         IServiceProvider serviceProvider,
         MovementController movementController,
+        GatheringController gatheringController,
         GatheringPointRegistry gatheringPointRegistry,
         IClientState clientState,
         GatheringData gatheringData,
@@ -50,8 +50,7 @@ internal static class GatheringRequiredItems
 
                 if (classJob != currentClassJob)
                 {
-                    yield return serviceProvider.GetRequiredService<SwitchClassJob>()
-                        .With(classJob);
+                    yield return new SwitchClassJob(classJob, clientState);
                 }
 
                 if (HasRequiredItems(requiredGatheredItems))
@@ -69,7 +68,7 @@ internal static class GatheringRequiredItems
                         foreach (var task in serviceProvider.GetRequiredService<TaskCreator>()
                                      .CreateTasks(quest, gatheringSequence, gatheringStep))
                             if (task is WaitAtEnd.NextStep)
-                                yield return serviceProvider.GetRequiredService<SkipMarker>();
+                                yield return CreateSkipMarkerTask();
                             else
                                 yield return task;
                     }
@@ -82,8 +81,7 @@ internal static class GatheringRequiredItems
                 yield return new WaitConditionTask(() => movementController.IsNavmeshReady,
                     "Wait(navmesh ready)");
 
-                yield return serviceProvider.GetRequiredService<StartGathering>()
-                    .With(gatheringPointId, requiredGatheredItems);
+                yield return CreateStartGatheringTask(gatheringPointId, requiredGatheredItems);
             }
         }
 
@@ -107,25 +105,28 @@ internal static class GatheringRequiredItems
                        minCollectability: (short)requiredGatheredItems.Collectability) >=
                    requiredGatheredItems.ItemCount;
         }
-    }
 
-    internal sealed class StartGathering(GatheringController gatheringController) : ITask
-    {
-        private GatheringPointId _gatheringPointId = null!;
-        private GatheredItem _gatheredItem = null!;
+        private StartGathering CreateStartGatheringTask(GatheringPointId gatheringPointId, GatheredItem gatheredItem)
+        {
+            return new StartGathering(gatheringPointId, gatheredItem, gatheringController);
+        }
 
-        public ITask With(GatheringPointId gatheringPointId, GatheredItem gatheredItem)
+        private static SkipMarker CreateSkipMarkerTask()
         {
-            _gatheringPointId = gatheringPointId;
-            _gatheredItem = gatheredItem;
-            return this;
+            return new SkipMarker();
         }
+    }
 
+    private sealed class StartGathering(
+        GatheringPointId gatheringPointId,
+        GatheredItem gatheredItem,
+        GatheringController gatheringController) : ITask
+    {
         public bool Start()
         {
-            return gatheringController.Start(new GatheringController.GatheringRequest(_gatheringPointId,
-                _gatheredItem.ItemId, _gatheredItem.AlternativeItemId, _gatheredItem.ItemCount,
-                _gatheredItem.Collectability));
+            return gatheringController.Start(new GatheringController.GatheringRequest(gatheringPointId,
+                gatheredItem.ItemId, gatheredItem.AlternativeItemId, gatheredItem.ItemCount,
+                gatheredItem.Collectability));
         }
 
         public ETaskResult Update()
@@ -138,11 +139,11 @@ internal static class GatheringRequiredItems
 
         public override string ToString()
         {
-            if (_gatheredItem.Collectability == 0)
-                return $"Gather({_gatheredItem.ItemCount}x {_gatheredItem.ItemId})";
+            if (gatheredItem.Collectability == 0)
+                return $"Gather({gatheredItem.ItemCount}x {gatheredItem.ItemId})";
             else
                 return
-                    $"Gather({_gatheredItem.ItemCount}x {_gatheredItem.ItemId} {SeIconChar.Collectible.ToIconString()} {_gatheredItem.Collectability})";
+                    $"Gather({gatheredItem.ItemCount}x {gatheredItem.ItemId} {SeIconChar.Collectible.ToIconString()} {gatheredItem.Collectability})";
         }
     }
 
diff --git a/Questionable/Controller/Steps/Shared/Move.cs b/Questionable/Controller/Steps/Shared/Move.cs
deleted file mode 100644 (file)
index 9d666f9..0000000
+++ /dev/null
@@ -1,337 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Numerics;
-using Dalamud.Game.ClientState.Conditions;
-using Dalamud.Game.ClientState.Objects.Types;
-using Dalamud.Game.Text.SeStringHandling;
-using Dalamud.Plugin.Services;
-using FFXIVClientStructs.FFXIV.Client.Game;
-using FFXIVClientStructs.FFXIV.Client.Game.Character;
-using LLib;
-using Lumina.Excel.GeneratedSheets;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using Questionable.Controller.Steps.Common;
-using Questionable.Data;
-using Questionable.Functions;
-using Questionable.Model;
-using Questionable.Model.Questing;
-using Action = System.Action;
-using Quest = Questionable.Model.Quest;
-
-namespace Questionable.Controller.Steps.Shared;
-
-internal static class Move
-{
-    internal sealed class Factory(IServiceProvider serviceProvider, AetheryteData aetheryteData) : ITaskFactory
-    {
-        public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
-        {
-            if (step.Position != null)
-            {
-                var builder = serviceProvider.GetRequiredService<MoveBuilder>()
-                    .With(quest.Id, step, step.Position.Value);
-                return builder.Build();
-            }
-            else if (step is { DataId: not null, StopDistance: not null })
-            {
-                var task = serviceProvider.GetRequiredService<ExpectToBeNearDataId>();
-                task.DataId = step.DataId.Value;
-                task.StopDistance = step.StopDistance.Value;
-                return [task];
-            }
-            else if (step is { InteractionType: EInteractionType.AttuneAetheryte, Aetheryte: not null })
-            {
-                var builder = serviceProvider.GetRequiredService<MoveBuilder>()
-                    .With(quest.Id, step, aetheryteData.Locations[step.Aetheryte.Value]);
-                return builder.Build();
-            }
-            else if (step is { InteractionType: EInteractionType.AttuneAethernetShard, AethernetShard: not null })
-            {
-                var builder = serviceProvider.GetRequiredService<MoveBuilder>().With(quest.Id, step,
-                    aetheryteData.Locations[step.AethernetShard.Value]);
-                return builder.Build();
-            }
-
-            return [];
-        }
-    }
-
-    internal sealed class MoveBuilder(
-        IServiceProvider serviceProvider,
-        ILogger<MoveBuilder> logger,
-        GameFunctions gameFunctions,
-        IClientState clientState,
-        MovementController movementController,
-        TerritoryData territoryData,
-        AetheryteData aetheryteData)
-    {
-        private ElementId _questId = null!;
-        private QuestStep _step = null!;
-        private Vector3 _destination;
-
-        public MoveBuilder With(ElementId questId, QuestStep step, Vector3 destination)
-        {
-            _questId = questId;
-            _step = step;
-            _destination = destination;
-            return this;
-        }
-
-        public IEnumerable<ITask> Build()
-        {
-            if (_step.InteractionType == EInteractionType.Jump && _step.JumpDestination != null &&
-                (clientState.LocalPlayer!.Position - _step.JumpDestination.Position).Length() <=
-                (_step.JumpDestination.StopDistance ?? 1f))
-            {
-                logger.LogInformation("We're at the jump destination, skipping movement");
-                yield break;
-            }
-
-            yield return new WaitConditionTask(() => clientState.TerritoryType == _step.TerritoryId,
-                $"Wait(territory: {territoryData.GetNameAndId(_step.TerritoryId)})");
-
-            if (!_step.DisableNavmesh)
-                yield return new WaitConditionTask(() => movementController.IsNavmeshReady,
-                    "Wait(navmesh ready)");
-
-            float stopDistance = _step.CalculateActualStopDistance();
-            Vector3? position = clientState.LocalPlayer?.Position;
-            float actualDistance = position == null ? float.MaxValue : Vector3.Distance(position.Value, _destination);
-
-            // if we teleport to a different zone, assume we always need to move; this is primarily relevant for cases
-            // where you're e.g. in Lakeland, and the step navigates via Crystarium → Tesselation back into the same
-            // zone.
-            //
-            // Side effects of this check being broken include:
-            //   - mounting when near the target npc (if you spawn close enough for the next step)
-            //   - trying to fly when near the target npc (if close enough where no movement is required)
-            if (_step.AetheryteShortcut != null &&
-                aetheryteData.TerritoryIds[_step.AetheryteShortcut.Value] != _step.TerritoryId)
-            {
-                logger.LogDebug("Aetheryte: Changing distance to max, previous distance: {Distance}", actualDistance);
-                actualDistance = float.MaxValue;
-            }
-
-            // In particular, MoveBuilder is used so early that it'll have the position when you're starting gathering,
-            // not when you're finished.
-            if (_questId is SatisfactionSupplyNpcId)
-            {
-                logger.LogDebug("SatisfactionSupply: Changing distance to max, previous distance: {Distance}",
-                    actualDistance);
-                actualDistance = float.MaxValue;
-            }
-
-            if (_step.Mount == true)
-                yield return serviceProvider.GetRequiredService<MountTask>()
-                    .With(_step.TerritoryId, MountTask.EMountIf.Always);
-            else if (_step.Mount == false)
-                yield return serviceProvider.GetRequiredService<UnmountTask>();
-
-            if (!_step.DisableNavmesh)
-            {
-                if (_step.Mount == null)
-                {
-                    MountTask.EMountIf mountIf =
-                        actualDistance > stopDistance && _step.Fly == true &&
-                        gameFunctions.IsFlyingUnlocked(_step.TerritoryId)
-                            ? MountTask.EMountIf.Always
-                            : MountTask.EMountIf.AwayFromPosition;
-                    yield return serviceProvider.GetRequiredService<MountTask>()
-                        .With(_step.TerritoryId, mountIf, _destination);
-                }
-
-                if (actualDistance > stopDistance)
-                {
-                    yield return serviceProvider.GetRequiredService<MoveInternal>()
-                        .With(_step, _destination);
-                }
-                else
-                    logger.LogInformation("Skipping move task, distance: {ActualDistance} < {StopDistance}",
-                        actualDistance, stopDistance);
-            }
-            else
-            {
-                // navmesh won't move close enough
-                if (actualDistance > stopDistance)
-                {
-                    yield return serviceProvider.GetRequiredService<MoveInternal>()
-                        .With(_step, _destination);
-                }
-                else
-                    logger.LogInformation("Skipping move task, distance: {ActualDistance} < {StopDistance}",
-                        actualDistance, stopDistance);
-            }
-
-            if (_step.Fly == true && _step.Land == true)
-                yield return serviceProvider.GetRequiredService<Land>();
-        }
-    }
-
-    internal sealed class MoveInternal(
-        MovementController movementController,
-        GameFunctions gameFunctions,
-        ILogger<MoveInternal> logger,
-        ICondition condition,
-        IDataManager dataManager) : ITask, IToastAware
-    {
-        private string _cannotExecuteAtThisTime = dataManager.GetString<LogMessage>(579, x => x.Text)!;
-
-        public Action StartAction { get; set; } = null!;
-        public Vector3 Destination { get; set; }
-
-        public ITask With(QuestStep step, Vector3 destination)
-        {
-            return With(
-                territoryId: step.TerritoryId,
-                destination: destination,
-                stopDistance: step.CalculateActualStopDistance(),
-                dataId: step.DataId,
-                disableNavMesh: step.DisableNavmesh,
-                sprint: step.Sprint != false,
-                fly: step.Fly == true,
-                land: step.Land == true,
-                ignoreDistanceToObject: step.IgnoreDistanceToObject == true);
-        }
-
-        public ITask With(ushort territoryId, Vector3 destination, float? stopDistance = null, uint? dataId = null,
-            bool disableNavMesh = false, bool sprint = true, bool fly = false, bool land = false,
-            bool ignoreDistanceToObject = false)
-        {
-            Destination = destination;
-
-            if (!gameFunctions.IsFlyingUnlocked(territoryId))
-            {
-                fly = false;
-                land = false;
-            }
-
-            if (!disableNavMesh)
-            {
-                StartAction = () =>
-                    movementController.NavigateTo(EMovementType.Quest, dataId, Destination,
-                        fly: fly,
-                        sprint: sprint,
-                        stopDistance: stopDistance,
-                        ignoreDistanceToObject: ignoreDistanceToObject,
-                        land: land);
-            }
-            else
-            {
-                StartAction = () =>
-                    movementController.NavigateTo(EMovementType.Quest, dataId, [Destination],
-                        fly: fly,
-                        sprint: sprint,
-                        stopDistance: stopDistance,
-                        ignoreDistanceToObject: ignoreDistanceToObject,
-                        land: land);
-            }
-
-            return this;
-        }
-
-        public bool Start()
-        {
-            logger.LogInformation("Moving to {Destination}", Destination.ToString("G", CultureInfo.InvariantCulture));
-            StartAction();
-            return true;
-        }
-
-        public ETaskResult Update()
-        {
-            if (movementController.IsPathfinding || movementController.IsPathRunning)
-                return ETaskResult.StillRunning;
-
-            DateTime movementStartedAt = movementController.MovementStartedAt;
-            if (movementStartedAt == DateTime.MaxValue || movementStartedAt.AddSeconds(2) >= DateTime.Now)
-                return ETaskResult.StillRunning;
-
-            return ETaskResult.TaskComplete;
-        }
-
-        public override string ToString() => $"MoveTo({Destination.ToString("G", CultureInfo.InvariantCulture)})";
-
-        public bool OnErrorToast(SeString message)
-        {
-            if (GameFunctions.GameStringEquals(_cannotExecuteAtThisTime, message.TextValue) &&
-                condition[ConditionFlag.Diving])
-                return true;
-
-            return false;
-        }
-    }
-
-    internal sealed class ExpectToBeNearDataId(GameFunctions gameFunctions, IClientState clientState) : ITask
-    {
-        public uint DataId { get; set; }
-        public float StopDistance { get; set; }
-
-        public bool Start() => true;
-
-        public ETaskResult Update()
-        {
-            IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId);
-            if (gameObject == null ||
-                (gameObject.Position - clientState.LocalPlayer!.Position).Length() > StopDistance)
-            {
-                throw new TaskException("Object not found or too far away, no position so we can't move");
-            }
-
-            return ETaskResult.TaskComplete;
-        }
-    }
-
-    internal sealed class Land(IClientState clientState, ICondition condition, ILogger<Land> logger) : ITask
-    {
-        private bool _landing;
-        private DateTime _continueAt;
-
-        public bool Start()
-        {
-            if (!condition[ConditionFlag.InFlight])
-            {
-                logger.LogInformation("Not flying, not attempting to land");
-                return false;
-            }
-
-            _landing = AttemptLanding();
-            _continueAt = DateTime.Now.AddSeconds(0.25);
-            return true;
-        }
-
-        public ETaskResult Update()
-        {
-            if (DateTime.Now < _continueAt)
-                return ETaskResult.StillRunning;
-
-            if (condition[ConditionFlag.InFlight])
-            {
-                if (!_landing)
-                {
-                    _landing = AttemptLanding();
-                    _continueAt = DateTime.Now.AddSeconds(0.25);
-                }
-
-                return ETaskResult.StillRunning;
-            }
-
-            return ETaskResult.TaskComplete;
-        }
-
-        private unsafe bool AttemptLanding()
-        {
-            var character = (Character*)(clientState.LocalPlayer?.Address ?? 0);
-            if (character != null)
-            {
-                if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 23) == 0)
-                {
-                    logger.LogInformation("Attempting to land");
-                    return ActionManager.Instance()->UseAction(ActionType.GeneralAction, 23);
-                }
-            }
-
-            return false;
-        }
-    }
-}
diff --git a/Questionable/Controller/Steps/Shared/MoveTo.cs b/Questionable/Controller/Steps/Shared/MoveTo.cs
new file mode 100644 (file)
index 0000000..1ba43fd
--- /dev/null
@@ -0,0 +1,349 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Numerics;
+using Dalamud.Game.ClientState.Conditions;
+using Dalamud.Game.ClientState.Objects.Types;
+using Dalamud.Game.Text.SeStringHandling;
+using Dalamud.Plugin.Services;
+using FFXIVClientStructs.FFXIV.Client.Game;
+using FFXIVClientStructs.FFXIV.Client.Game.Character;
+using LLib;
+using Lumina.Excel.GeneratedSheets;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Questionable.Controller.Steps.Common;
+using Questionable.Data;
+using Questionable.Functions;
+using Questionable.Model;
+using Questionable.Model.Questing;
+using Action = System.Action;
+using Mount = Questionable.Controller.Steps.Common.Mount;
+using Quest = Questionable.Model.Quest;
+
+namespace Questionable.Controller.Steps.Shared;
+
+internal static class MoveTo
+{
+    internal sealed class Factory(
+        MovementController movementController,
+        GameFunctions gameFunctions,
+        ICondition condition,
+        IDataManager dataManager,
+        IClientState clientState,
+        AetheryteData aetheryteData,
+        TerritoryData territoryData,
+        ILoggerFactory loggerFactory,
+        Mount.Factory mountFactory,
+        ILogger<Factory> logger) : ITaskFactory
+    {
+        public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
+        {
+            if (step.Position != null)
+            {
+                return CreateMountTasks(quest.Id, step, step.Position.Value);
+            }
+            else if (step is { DataId: not null, StopDistance: not null })
+            {
+                return [ExpectToBeNearDataId(step.DataId.Value, step.StopDistance.Value)];
+            }
+            else if (step is { InteractionType: EInteractionType.AttuneAetheryte, Aetheryte: not null })
+            {
+                return CreateMountTasks(quest.Id, step, aetheryteData.Locations[step.Aetheryte.Value]);
+            }
+            else if (step is { InteractionType: EInteractionType.AttuneAethernetShard, AethernetShard: not null })
+            {
+                return CreateMountTasks(quest.Id, step, aetheryteData.Locations[step.AethernetShard.Value]);
+            }
+
+            return [];
+        }
+
+        public ITask Move(QuestStep step, Vector3 destination)
+        {
+            return Move(new MoveParams(step, destination));
+        }
+
+        public ITask Move(MoveParams moveParams)
+        {
+            return new MoveInternal(moveParams, movementController, gameFunctions,
+                loggerFactory.CreateLogger<MoveInternal>(), condition, dataManager);
+        }
+
+        public ITask Land()
+        {
+            return new LandTask(clientState, condition, loggerFactory.CreateLogger<LandTask>());
+        }
+
+        public ITask ExpectToBeNearDataId(uint dataId, float stopDistance)
+        {
+            return new WaitForNearDataId(dataId, stopDistance, gameFunctions, clientState);
+        }
+
+        public IEnumerable<ITask> CreateMountTasks(ElementId questId, QuestStep step, Vector3 destination)
+        {
+            if (step.InteractionType == EInteractionType.Jump && step.JumpDestination != null &&
+                (clientState.LocalPlayer!.Position - step.JumpDestination.Position).Length() <=
+                (step.JumpDestination.StopDistance ?? 1f))
+            {
+                logger.LogInformation("We're at the jump destination, skipping movement");
+                yield break;
+            }
+
+            yield return new WaitConditionTask(() => clientState.TerritoryType == step.TerritoryId,
+                $"Wait(territory: {territoryData.GetNameAndId(step.TerritoryId)})");
+
+            if (!step.DisableNavmesh)
+                yield return new WaitConditionTask(() => movementController.IsNavmeshReady,
+                    "Wait(navmesh ready)");
+
+            float stopDistance = step.CalculateActualStopDistance();
+            Vector3? position = clientState.LocalPlayer?.Position;
+            float actualDistance = position == null ? float.MaxValue : Vector3.Distance(position.Value, destination);
+
+            // if we teleport to a different zone, assume we always need to move; this is primarily relevant for cases
+            // where you're e.g. in Lakeland, and the step navigates via Crystarium → Tesselation back into the same
+            // zone.
+            //
+            // Side effects of this check being broken include:
+            //   - mounting when near the target npc (if you spawn close enough for the next step)
+            //   - trying to fly when near the target npc (if close enough where no movement is required)
+            if (step.AetheryteShortcut != null &&
+                aetheryteData.TerritoryIds[step.AetheryteShortcut.Value] != step.TerritoryId)
+            {
+                logger.LogDebug("Aetheryte: Changing distance to max, previous distance: {Distance}", actualDistance);
+                actualDistance = float.MaxValue;
+            }
+
+            // In particular, MoveBuilder is used so early that it'll have the position when you're starting gathering,
+            // not when you're finished.
+            if (questId is SatisfactionSupplyNpcId)
+            {
+                logger.LogDebug("SatisfactionSupply: Changing distance to max, previous distance: {Distance}",
+                    actualDistance);
+                actualDistance = float.MaxValue;
+            }
+
+            if (step.Mount == true)
+                yield return mountFactory.Mount(step.TerritoryId, Mount.EMountIf.Always);
+            else if (step.Mount == false)
+                yield return mountFactory.Unmount();
+
+            if (!step.DisableNavmesh)
+            {
+                if (step.Mount == null)
+                {
+                    Mount.EMountIf mountIf =
+                        actualDistance > stopDistance && step.Fly == true &&
+                        gameFunctions.IsFlyingUnlocked(step.TerritoryId)
+                            ? Mount.EMountIf.Always
+                            : Mount.EMountIf.AwayFromPosition;
+                    yield return mountFactory.Mount(step.TerritoryId, mountIf, destination);
+                }
+
+                if (actualDistance > stopDistance)
+                {
+                    yield return Move(step, destination);
+                }
+                else
+                    logger.LogInformation("Skipping move task, distance: {ActualDistance} < {StopDistance}",
+                        actualDistance, stopDistance);
+            }
+            else
+            {
+                // navmesh won't move close enough
+                if (actualDistance > stopDistance)
+                {
+                    yield return Move(step, destination);
+                }
+                else
+                    logger.LogInformation("Skipping move task, distance: {ActualDistance} < {StopDistance}",
+                        actualDistance, stopDistance);
+            }
+
+            if (step.Fly == true && step.Land == true)
+                yield return Land();
+        }
+    }
+
+    private sealed class MoveInternal : ITask, IToastAware
+    {
+        private readonly string _cannotExecuteAtThisTime;
+        private readonly MovementController _movementController;
+        private readonly ILogger<MoveInternal> _logger;
+        private readonly ICondition _condition;
+
+        private readonly Action _startAction;
+        private readonly Vector3 _destination;
+
+        public MoveInternal(MoveParams moveParams,
+            MovementController movementController,
+            GameFunctions gameFunctions,
+            ILogger<MoveInternal> logger,
+            ICondition condition,
+            IDataManager dataManager)
+        {
+            _movementController = movementController;
+            _logger = logger;
+            _condition = condition;
+            _cannotExecuteAtThisTime = dataManager.GetString<LogMessage>(579, x => x.Text)!;
+
+            _destination = moveParams.Destination;
+
+            if (!gameFunctions.IsFlyingUnlocked(moveParams.TerritoryId))
+            {
+                moveParams = moveParams with { Fly = false, Land = false };
+            }
+
+            if (!moveParams.DisableNavMesh)
+            {
+                _startAction = () =>
+                    _movementController.NavigateTo(EMovementType.Quest, moveParams.DataId, _destination,
+                        fly: moveParams.Fly,
+                        sprint: moveParams.Sprint,
+                        stopDistance: moveParams.StopDistance,
+                        ignoreDistanceToObject: moveParams.IgnoreDistanceToObject,
+                        land: moveParams.Land);
+            }
+            else
+            {
+                _startAction = () =>
+                    _movementController.NavigateTo(EMovementType.Quest, moveParams.DataId, [_destination],
+                        fly: moveParams.Fly,
+                        sprint: moveParams.Sprint,
+                        stopDistance: moveParams.StopDistance,
+                        ignoreDistanceToObject: moveParams.IgnoreDistanceToObject,
+                        land: moveParams.Land);
+            }
+        }
+
+        public bool Start()
+        {
+            _logger.LogInformation("Moving to {Destination}", _destination.ToString("G", CultureInfo.InvariantCulture));
+            _startAction();
+            return true;
+        }
+
+        public ETaskResult Update()
+        {
+            if (_movementController.IsPathfinding || _movementController.IsPathRunning)
+                return ETaskResult.StillRunning;
+
+            DateTime movementStartedAt = _movementController.MovementStartedAt;
+            if (movementStartedAt == DateTime.MaxValue || movementStartedAt.AddSeconds(2) >= DateTime.Now)
+                return ETaskResult.StillRunning;
+
+            return ETaskResult.TaskComplete;
+        }
+
+        public override string ToString() => $"MoveTo({_destination.ToString("G", CultureInfo.InvariantCulture)})";
+
+        public bool OnErrorToast(SeString message)
+        {
+            if (GameFunctions.GameStringEquals(_cannotExecuteAtThisTime, message.TextValue) &&
+                _condition[ConditionFlag.Diving])
+                return true;
+
+            return false;
+        }
+    }
+
+    internal sealed record MoveParams(
+        ushort TerritoryId,
+        Vector3 Destination,
+        float? StopDistance = null,
+        uint? DataId = null,
+        bool DisableNavMesh = false,
+        bool Sprint = true,
+        bool Fly = false,
+        bool Land = false,
+        bool IgnoreDistanceToObject = false)
+    {
+        public MoveParams(QuestStep step, Vector3 destination)
+            : this(step.TerritoryId,
+                destination,
+                step.CalculateActualStopDistance(),
+                step.DataId,
+                step.DisableNavmesh,
+                step.Sprint != false,
+                step.Fly == true,
+                step.Land == true,
+                step.IgnoreDistanceToObject == true)
+        {
+        }
+    }
+
+    private sealed class WaitForNearDataId(
+        uint dataId,
+        float stopDistance,
+        GameFunctions gameFunctions,
+        IClientState clientState) : ITask
+    {
+        public bool Start() => true;
+
+        public ETaskResult Update()
+        {
+            IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId);
+            if (gameObject == null ||
+                (gameObject.Position - clientState.LocalPlayer!.Position).Length() > stopDistance)
+            {
+                throw new TaskException("Object not found or too far away, no position so we can't move");
+            }
+
+            return ETaskResult.TaskComplete;
+        }
+    }
+
+    private sealed class LandTask(IClientState clientState, ICondition condition, ILogger<LandTask> logger) : ITask
+    {
+        private bool _landing;
+        private DateTime _continueAt;
+
+        public bool Start()
+        {
+            if (!condition[ConditionFlag.InFlight])
+            {
+                logger.LogInformation("Not flying, not attempting to land");
+                return false;
+            }
+
+            _landing = AttemptLanding();
+            _continueAt = DateTime.Now.AddSeconds(0.25);
+            return true;
+        }
+
+        public ETaskResult Update()
+        {
+            if (DateTime.Now < _continueAt)
+                return ETaskResult.StillRunning;
+
+            if (condition[ConditionFlag.InFlight])
+            {
+                if (!_landing)
+                {
+                    _landing = AttemptLanding();
+                    _continueAt = DateTime.Now.AddSeconds(0.25);
+                }
+
+                return ETaskResult.StillRunning;
+            }
+
+            return ETaskResult.TaskComplete;
+        }
+
+        private unsafe bool AttemptLanding()
+        {
+            var character = (Character*)(clientState.LocalPlayer?.Address ?? 0);
+            if (character != null)
+            {
+                if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 23) == 0)
+                {
+                    logger.LogInformation("Attempting to land");
+                    return ActionManager.Instance()->UseAction(ActionType.GeneralAction, 23);
+                }
+            }
+
+            return false;
+        }
+    }
+}
index 69d441490482d0b208137ae519f742bc0d275dae..1d2420cfb85a011331443d388723958164a4833b 100644 (file)
@@ -20,7 +20,12 @@ namespace Questionable.Controller.Steps.Shared;
 
 internal static class SkipCondition
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+    internal sealed class Factory(
+        ILoggerFactory loggerFactory,
+        AetheryteFunctions aetheryteFunctions,
+        GameFunctions gameFunctions,
+        QuestFunctions questFunctions,
+        IClientState clientState) : SimpleTaskFactory
     {
         public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
@@ -35,90 +40,86 @@ internal static class SkipCondition
                 step.NextQuestId == null)
                 return null;
 
-            return serviceProvider.GetRequiredService<CheckSkip>()
-                .With(step, skipConditions ?? new(), quest.Id);
+            return Check(step, skipConditions, quest.Id);
+        }
+
+        private CheckSkip Check(QuestStep step, SkipStepConditions? skipConditions, ElementId questId)
+        {
+            return new CheckSkip(step, skipConditions ?? new(), questId, loggerFactory.CreateLogger<CheckSkip>(),
+                aetheryteFunctions, gameFunctions, questFunctions, clientState);
         }
     }
 
-    internal sealed class CheckSkip(
+    private sealed class CheckSkip(
+        QuestStep step,
+        SkipStepConditions skipConditions,
+        ElementId elementId,
         ILogger<CheckSkip> logger,
         AetheryteFunctions aetheryteFunctions,
         GameFunctions gameFunctions,
         QuestFunctions questFunctions,
         IClientState clientState) : ITask
     {
-        public QuestStep Step { get; set; } = null!;
-        public SkipStepConditions SkipConditions { get; set; } = null!;
-        public ElementId ElementId { get; set; } = null!;
-
-        public ITask With(QuestStep step, SkipStepConditions skipConditions, ElementId elementId)
-        {
-            Step = step;
-            SkipConditions = skipConditions;
-            ElementId = elementId;
-            return this;
-        }
-
         public unsafe bool Start()
         {
-            logger.LogInformation("Checking skip conditions; {ConfiguredConditions}", string.Join(",", SkipConditions));
+            logger.LogInformation("Checking skip conditions; {ConfiguredConditions}", string.Join(",", skipConditions));
 
-            if (SkipConditions.Flying == ELockedSkipCondition.Unlocked &&
-                gameFunctions.IsFlyingUnlocked(Step.TerritoryId))
+            if (skipConditions.Flying == ELockedSkipCondition.Unlocked &&
+                gameFunctions.IsFlyingUnlocked(step.TerritoryId))
             {
                 logger.LogInformation("Skipping step, as flying is unlocked");
                 return true;
             }
 
-            if (SkipConditions.Flying == ELockedSkipCondition.Locked &&
-                !gameFunctions.IsFlyingUnlocked(Step.TerritoryId))
+            if (skipConditions.Flying == ELockedSkipCondition.Locked &&
+                !gameFunctions.IsFlyingUnlocked(step.TerritoryId))
             {
                 logger.LogInformation("Skipping step, as flying is locked");
                 return true;
             }
 
-            if (SkipConditions.Chocobo == ELockedSkipCondition.Unlocked &&
+            if (skipConditions.Chocobo == ELockedSkipCondition.Unlocked &&
                 PlayerState.Instance()->IsMountUnlocked(1))
             {
                 logger.LogInformation("Skipping step, as chocobo is unlocked");
                 return true;
             }
 
-            if (SkipConditions.InTerritory.Count > 0 &&
-                SkipConditions.InTerritory.Contains(clientState.TerritoryType))
+            if (skipConditions.InTerritory.Count > 0 &&
+                skipConditions.InTerritory.Contains(clientState.TerritoryType))
             {
                 logger.LogInformation("Skipping step, as in a skip.InTerritory");
                 return true;
             }
 
-            if (SkipConditions.NotInTerritory.Count > 0 &&
-                !SkipConditions.NotInTerritory.Contains(clientState.TerritoryType))
+            if (skipConditions.NotInTerritory.Count > 0 &&
+                !skipConditions.NotInTerritory.Contains(clientState.TerritoryType))
             {
                 logger.LogInformation("Skipping step, as not in a skip.NotInTerritory");
                 return true;
             }
 
-            if (SkipConditions.QuestsCompleted.Count > 0 &&
-                SkipConditions.QuestsCompleted.All(questFunctions.IsQuestComplete))
+            if (skipConditions.QuestsCompleted.Count > 0 &&
+                skipConditions.QuestsCompleted.All(questFunctions.IsQuestComplete))
             {
                 logger.LogInformation("Skipping step, all prequisite quests are complete");
                 return true;
             }
 
-            if (SkipConditions.QuestsAccepted.Count > 0 &&
-                SkipConditions.QuestsAccepted.All(questFunctions.IsQuestAccepted))
+            if (skipConditions.QuestsAccepted.Count > 0 &&
+                skipConditions.QuestsAccepted.All(questFunctions.IsQuestAccepted))
             {
                 logger.LogInformation("Skipping step, all prequisite quests are accepted");
                 return true;
             }
 
-            if (SkipConditions.NotTargetable &&
-                Step is { DataId: not null })
+            if (skipConditions.NotTargetable &&
+                step is { DataId: not null })
             {
-                IGameObject? gameObject = gameFunctions.FindObjectByDataId(Step.DataId.Value);
+                IGameObject? gameObject = gameFunctions.FindObjectByDataId(step.DataId.Value);
                 if (gameObject == null)
                 {
-                    if ((Step.Position.GetValueOrDefault() - clientState.LocalPlayer!.Position).Length() < 100)
+                    if ((step.Position.GetValueOrDefault() - clientState.LocalPlayer!.Position).Length() < 100)
                     {
                         logger.LogInformation("Skipping step, object is not nearby (but we are)");
                         return true;
@@ -131,60 +132,60 @@ internal static class SkipCondition
                 }
             }
 
-            if (SkipConditions.Item is { NotInInventory: true } && Step is { ItemId: not null })
+            if (skipConditions.Item is { NotInInventory: true } && step is { ItemId: not null })
             {
                 InventoryManager* inventoryManager = InventoryManager.Instance();
-                if (inventoryManager->GetInventoryItemCount(Step.ItemId.Value) == 0)
+                if (inventoryManager->GetInventoryItemCount(step.ItemId.Value) == 0)
                 {
                     logger.LogInformation("Skipping step, no item with itemId {ItemId} in inventory",
-                        Step.ItemId.Value);
+                        step.ItemId.Value);
                     return true;
                 }
             }
 
-            if (Step is
+            if (step is
                 {
                     DataId: not null,
                     InteractionType: EInteractionType.AttuneAetheryte or EInteractionType.AttuneAethernetShard
                 } &&
-                aetheryteFunctions.IsAetheryteUnlocked((EAetheryteLocation)Step.DataId.Value))
+                aetheryteFunctions.IsAetheryteUnlocked((EAetheryteLocation)step.DataId.Value))
             {
                 logger.LogInformation("Skipping step, as aetheryte/aethernet shard is unlocked");
                 return true;
             }
 
-            if (SkipConditions.AetheryteLocked != null &&
-                !aetheryteFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteLocked.Value))
+            if (skipConditions.AetheryteLocked != null &&
+                !aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteLocked.Value))
             {
                 logger.LogInformation("Skipping step, as aetheryte is locked");
                 return true;
             }
 
-            if (SkipConditions.AetheryteUnlocked != null &&
-                aetheryteFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteUnlocked.Value))
+            if (skipConditions.AetheryteUnlocked != null &&
+                aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteUnlocked.Value))
             {
                 logger.LogInformation("Skipping step, as aetheryte is unlocked");
                 return true;
             }
 
-            if (Step is { DataId: not null, InteractionType: EInteractionType.AttuneAetherCurrent } &&
-                gameFunctions.IsAetherCurrentUnlocked(Step.DataId.Value))
+            if (step is { DataId: not null, InteractionType: EInteractionType.AttuneAetherCurrent } &&
+                gameFunctions.IsAetherCurrentUnlocked(step.DataId.Value))
             {
                 logger.LogInformation("Skipping step, as current is unlocked");
                 return true;
             }
 
-            QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(ElementId);
+            QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(elementId);
             if (questWork != null)
             {
-                if (QuestWorkUtils.HasCompletionFlags(Step.CompletionQuestVariablesFlags) &&
-                    QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork))
+                if (QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags) &&
+                    QuestWorkUtils.MatchesQuestWork(step.CompletionQuestVariablesFlags, questWork))
                 {
                     logger.LogInformation("Skipping step, as quest variables match (step is complete)");
                     return true;
                 }
 
-                if (Step is { SkipConditions.StepIf: { } conditions })
+                if (step is { SkipConditions.StepIf: { } conditions })
                 {
                     if (QuestWorkUtils.MatchesQuestWork(conditions.CompletionQuestVariablesFlags, questWork))
                     {
@@ -193,7 +194,7 @@ internal static class SkipCondition
                     }
                 }
 
-                if (Step is { RequiredQuestVariables: { } requiredQuestVariables })
+                if (step is { RequiredQuestVariables: { } requiredQuestVariables })
                 {
                     if (!QuestWorkUtils.MatchesRequiredQuestWorkConfig(requiredQuestVariables, questWork, logger))
                     {
@@ -203,16 +204,17 @@ internal static class SkipCondition
                 }
             }
 
-            if (SkipConditions.NearPosition is { } nearPosition && clientState.TerritoryType == Step.TerritoryId)
+            if (skipConditions.NearPosition is { } nearPosition && clientState.TerritoryType == step.TerritoryId)
             {
-                if (Vector3.Distance(nearPosition.Position, clientState.LocalPlayer!.Position) <= nearPosition.MaximumDistance)
+                if (Vector3.Distance(nearPosition.Position, clientState.LocalPlayer!.Position) <=
+                    nearPosition.MaximumDistance)
                 {
                     logger.LogInformation("Skipping step, as we're near the position");
                     return true;
                 }
             }
 
-            if (SkipConditions.ExtraCondition == EExtraSkipCondition.WakingSandsMainArea &&
+            if (skipConditions.ExtraCondition == EExtraSkipCondition.WakingSandsMainArea &&
                 clientState.TerritoryType == 212)
             {
                 var position = clientState.LocalPlayer!.Position;
@@ -223,7 +225,7 @@ internal static class SkipCondition
                 }
             }
 
-            if (SkipConditions.ExtraCondition == EExtraSkipCondition.RisingStonesSolar &&
+            if (skipConditions.ExtraCondition == EExtraSkipCondition.RisingStonesSolar &&
                 clientState.TerritoryType == 351)
             {
                 var position = clientState.LocalPlayer!.Position;
@@ -234,13 +236,13 @@ internal static class SkipCondition
                 }
             }
 
-            if (Step.PickUpQuestId != null && questFunctions.IsQuestAcceptedOrComplete(Step.PickUpQuestId))
+            if (step.PickUpQuestId != null && questFunctions.IsQuestAcceptedOrComplete(step.PickUpQuestId))
             {
                 logger.LogInformation("Skipping step, as we have already picked up the relevant quest");
                 return true;
             }
 
-            if (Step.TurnInQuestId != null && questFunctions.IsQuestComplete(Step.TurnInQuestId))
+            if (step.TurnInQuestId != null && questFunctions.IsQuestComplete(step.TurnInQuestId))
             {
                 logger.LogInformation("Skipping step, as we have already completed the relevant quest");
                 return true;
index 430a8981fffcd47c4ca0834423068f27778438df..86487621834062bb939e59e90672241c5c777258 100644 (file)
@@ -1,6 +1,4 @@
-using System;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging;
 using Questionable.Model;
 using Questionable.Model.Questing;
 
@@ -8,14 +6,14 @@ namespace Questionable.Controller.Steps.Shared;
 
 internal static class StepDisabled
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+    internal sealed class Factory(ILoggerFactory loggerFactory) : SimpleTaskFactory
     {
         public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (!step.Disabled)
                 return null;
 
-            return serviceProvider.GetRequiredService<Task>();
+            return new Task(loggerFactory.CreateLogger<Task>());
         }
     }
 
index c8159b93d590e149a7ec2510389fea3c35765a53..4a4fa4c0d046855909c5b4606b2618421ba76a85 100644 (file)
@@ -6,19 +6,11 @@ using Questionable.Controller.Steps.Common;
 
 namespace Questionable.Controller.Steps.Shared;
 
-internal sealed class SwitchClassJob(IClientState clientState) : AbstractDelayedTask
+internal sealed class SwitchClassJob(EClassJob classJob, IClientState clientState) : AbstractDelayedTask
 {
-    private EClassJob _classJob;
-
-    public ITask With(EClassJob classJob)
-    {
-        _classJob = classJob;
-        return this;
-    }
-
     protected override unsafe bool StartInternal()
     {
-        if (clientState.LocalPlayer!.ClassJob.Id == (uint)_classJob)
+        if (clientState.LocalPlayer!.ClassJob.Id == (uint)classJob)
             return false;
 
         var gearsetModule = RaptureGearsetModule.Instance();
@@ -27,7 +19,7 @@ internal sealed class SwitchClassJob(IClientState clientState) : AbstractDelayed
             for (int i = 0; i < 100; ++i)
             {
                 var gearset = gearsetModule->GetGearset(i);
-                if (gearset->ClassJob == (byte)_classJob)
+                if (gearset->ClassJob == (byte)classJob)
                 {
                     gearsetModule->EquipGearset(gearset->Id, gearset->BannerIndex);
                     return true;
@@ -35,10 +27,10 @@ internal sealed class SwitchClassJob(IClientState clientState) : AbstractDelayed
             }
         }
 
-        throw new TaskException($"No gearset found for {_classJob}");
+        throw new TaskException($"No gearset found for {classJob}");
     }
 
     protected override ETaskResult UpdateInternal() => ETaskResult.TaskComplete;
 
-    public override string ToString() => $"SwitchJob({_classJob})";
+    public override string ToString() => $"SwitchJob({classJob})";
 }
index faa36e004a38fe6154e3810181caec81d573c188..631cbf1148cd998c7a241f6b3d73ba9578a50adb 100644 (file)
@@ -5,9 +5,6 @@ using System.Linq;
 using System.Numerics;
 using Dalamud.Game.ClientState.Conditions;
 using Dalamud.Plugin.Services;
-using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
-using FFXIVClientStructs.FFXIV.Client.Game;
-using Microsoft.Extensions.DependencyInjection;
 using Questionable.Controller.Steps.Common;
 using Questionable.Controller.Utils;
 using Questionable.Data;
@@ -20,19 +17,20 @@ namespace Questionable.Controller.Steps.Shared;
 internal static class WaitAtEnd
 {
     internal sealed class Factory(
-        IServiceProvider serviceProvider,
         IClientState clientState,
         ICondition condition,
-        TerritoryData territoryData)
+        TerritoryData territoryData,
+        QuestFunctions questFunctions,
+        GameFunctions gameFunctions)
         : ITaskFactory
     {
         public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
         {
-            if (step.CompletionQuestVariablesFlags.Count == 6 && QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
+            if (step.CompletionQuestVariablesFlags.Count == 6 &&
+                QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
             {
-                var task = serviceProvider.GetRequiredService<WaitForCompletionFlags>()
-                    .With((QuestId)quest.Id, step);
-                var delay = serviceProvider.GetRequiredService<WaitDelay>();
+                var task = new WaitForCompletionFlags((QuestId)quest.Id, step, questFunctions);
+                var delay = new WaitDelay();
                 return [task, delay, Next(quest, sequence)];
             }
 
@@ -43,15 +41,15 @@ internal static class WaitAtEnd
                         new WaitConditionTask(() => !condition[ConditionFlag.InCombat], "Wait(not in combat)");
                     return
                     [
-                        serviceProvider.GetRequiredService<WaitDelay>(),
+                        new WaitDelay(),
                         notInCombat,
-                        serviceProvider.GetRequiredService<WaitDelay>(),
+                        new WaitDelay(),
                         Next(quest, sequence)
                     ];
 
                 case EInteractionType.WaitForManualProgress:
                 case EInteractionType.Instruction:
-                    return [serviceProvider.GetRequiredService<WaitNextStepOrSequence>()];
+                    return [new WaitNextStepOrSequence()];
 
                 case EInteractionType.Duty:
                 case EInteractionType.SinglePlayerDuty:
@@ -68,9 +66,9 @@ internal static class WaitAtEnd
 
                     return
                     [
-                        serviceProvider.GetRequiredService<WaitObjectAtPosition>()
-                            .With(step.DataId.Value, step.Position.Value, step.NpcWaitDistance ?? 0.05f),
-                        serviceProvider.GetRequiredService<WaitDelay>(),
+                        new WaitObjectAtPosition(step.DataId.Value, step.Position.Value, step.NpcWaitDistance ?? 0.05f,
+                            gameFunctions),
+                        new WaitDelay(),
                         Next(quest, sequence)
                     ];
 
@@ -104,15 +102,14 @@ internal static class WaitAtEnd
                     return
                     [
                         waitInteraction,
-                        serviceProvider.GetRequiredService<WaitDelay>(),
+                        new WaitDelay(),
                         Next(quest, sequence)
                     ];
 
                 case EInteractionType.AcceptQuest:
                 {
-                    var accept = serviceProvider.GetRequiredService<WaitQuestAccepted>()
-                        .With(step.PickUpQuestId ?? quest.Id);
-                    var delay = serviceProvider.GetRequiredService<WaitDelay>();
+                    var accept = new WaitQuestAccepted(step.PickUpQuestId ?? quest.Id, questFunctions);
+                    var delay = new WaitDelay();
                     if (step.PickUpQuestId != null)
                         return [accept, delay, Next(quest, sequence)];
                     else
@@ -121,9 +118,8 @@ internal static class WaitAtEnd
 
                 case EInteractionType.CompleteQuest:
                 {
-                    var complete = serviceProvider.GetRequiredService<WaitQuestCompleted>()
-                        .With(step.TurnInQuestId ?? quest.Id);
-                    var delay = serviceProvider.GetRequiredService<WaitDelay>();
+                    var complete = new WaitQuestCompleted(step.TurnInQuestId ?? quest.Id, questFunctions);
+                    var delay = new WaitDelay();
                     if (step.TurnInQuestId != null)
                         return [complete, delay, Next(quest, sequence)];
                     else
@@ -132,7 +128,7 @@ internal static class WaitAtEnd
 
                 case EInteractionType.Interact:
                 default:
-                    return [serviceProvider.GetRequiredService<WaitDelay>(), Next(quest, sequence)];
+                    return [new WaitDelay(), Next(quest, sequence)];
             }
         }
 
@@ -142,14 +138,8 @@ internal static class WaitAtEnd
         }
     }
 
-    internal sealed class WaitDelay() : AbstractDelayedTask(TimeSpan.FromSeconds(1))
+    internal sealed class WaitDelay(TimeSpan? delay = null) : AbstractDelayedTask(delay ?? TimeSpan.FromSeconds(1))
     {
-        public ITask With(TimeSpan delay)
-        {
-            Delay = delay;
-            return this;
-        }
-
         protected override bool StartInternal() => true;
 
         public override string ToString() => $"Wait(seconds: {Delay.TotalSeconds})";
@@ -164,100 +154,64 @@ internal static class WaitAtEnd
         public override string ToString() => "Wait(next step or sequence)";
     }
 
-    internal sealed class WaitForCompletionFlags(QuestFunctions questFunctions) : ITask
+    internal sealed class WaitForCompletionFlags(QuestId quest, QuestStep step, QuestFunctions questFunctions) : ITask
     {
-        public QuestId Quest { get; set; } = null!;
-        public QuestStep Step { get; set; } = null!;
-        public IList<QuestWorkValue?> Flags { get; set; } = null!;
-
-        public ITask With(QuestId quest, QuestStep step)
-        {
-            Quest = quest;
-            Step = step;
-            Flags = step.CompletionQuestVariablesFlags;
-            return this;
-        }
-
         public bool Start() => true;
 
         public ETaskResult Update()
         {
-            QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(Quest);
+            QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(quest);
             return questWork != null &&
-                   QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork)
+                   QuestWorkUtils.MatchesQuestWork(step.CompletionQuestVariablesFlags, questWork)
                 ? ETaskResult.TaskComplete
                 : ETaskResult.StillRunning;
         }
 
         public override string ToString() =>
-            $"Wait(QW: {string.Join(", ", Flags.Select(x => x?.ToString() ?? "-"))})";
+            $"Wait(QW: {string.Join(", ", step.CompletionQuestVariablesFlags.Select(x => x?.ToString() ?? "-"))})";
     }
 
-    internal sealed class WaitObjectAtPosition(GameFunctions gameFunctions) : ITask
+    private sealed class WaitObjectAtPosition(
+        uint dataId,
+        Vector3 destination,
+        float distance,
+        GameFunctions gameFunctions) : ITask
     {
-        public uint DataId { get; set; }
-        public Vector3 Destination { get; set; }
-        public float Distance { get; set; }
-
-        public ITask With(uint dataId, Vector3 destination, float distance)
-        {
-            DataId = dataId;
-            Destination = destination;
-            Distance = distance;
-            return this;
-        }
-
         public bool Start() => true;
 
         public ETaskResult Update() =>
-            gameFunctions.IsObjectAtPosition(DataId, Destination, Distance)
+            gameFunctions.IsObjectAtPosition(dataId, destination, distance)
                 ? ETaskResult.TaskComplete
                 : ETaskResult.StillRunning;
 
         public override string ToString() =>
-            $"WaitObj({DataId} at {Destination.ToString("G", CultureInfo.InvariantCulture)} < {Distance})";
+            $"WaitObj({dataId} at {destination.ToString("G", CultureInfo.InvariantCulture)} < {distance})";
     }
 
-    internal sealed class WaitQuestAccepted(QuestFunctions questFunctions) : ITask
+    internal sealed class WaitQuestAccepted(ElementId elementId, QuestFunctions questFunctions) : ITask
     {
-        public ElementId ElementId { get; set; } = null!;
-
-        public ITask With(ElementId elementId)
-        {
-            ElementId = elementId;
-            return this;
-        }
-
         public bool Start() => true;
 
         public ETaskResult Update()
         {
-            return questFunctions.IsQuestAccepted(ElementId)
+            return questFunctions.IsQuestAccepted(elementId)
                 ? ETaskResult.TaskComplete
                 : ETaskResult.StillRunning;
         }
 
-        public override string ToString() => $"WaitQuestAccepted({ElementId})";
+        public override string ToString() => $"WaitQuestAccepted({elementId})";
     }
 
-    internal sealed class WaitQuestCompleted(QuestFunctions questFunctions) : ITask
+    internal sealed class WaitQuestCompleted(ElementId elementId, QuestFunctions questFunctions) : ITask
     {
-        public ElementId ElementId { get; set; } = null!;
-
-        public ITask With(ElementId elementId)
-        {
-            ElementId = elementId;
-            return this;
-        }
-
         public bool Start() => true;
 
         public ETaskResult Update()
         {
-            return questFunctions.IsQuestComplete(ElementId) ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
+            return questFunctions.IsQuestComplete(elementId) ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
         }
 
-        public override string ToString() => $"WaitQuestComplete({ElementId})";
+        public override string ToString() => $"WaitQuestComplete({elementId})";
     }
 
     internal sealed class NextStep(ElementId elementId, int sequence) : ILastTask
index 1e7944c571d59295aa0134fcce6d413d24921f19..cbe63e7f219ac545c75713ff142cbf9b246af947 100644 (file)
@@ -1,5 +1,4 @@
 using System;
-using Microsoft.Extensions.DependencyInjection;
 using Questionable.Controller.Steps.Common;
 using Questionable.Model;
 using Questionable.Model.Questing;
@@ -8,26 +7,19 @@ namespace Questionable.Controller.Steps.Shared;
 
 internal static class WaitAtStart
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
+    internal sealed class Factory : SimpleTaskFactory
     {
         public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (step.DelaySecondsAtStart == null)
                 return null;
 
-            return serviceProvider.GetRequiredService<WaitDelay>()
-                .With(TimeSpan.FromSeconds(step.DelaySecondsAtStart.Value));
+            return new WaitDelay(TimeSpan.FromSeconds(step.DelaySecondsAtStart.Value));
         }
     }
 
-    internal sealed class WaitDelay : AbstractDelayedTask
+    internal sealed class WaitDelay(TimeSpan delay) : AbstractDelayedTask(delay)
     {
-        public ITask With(TimeSpan delay)
-        {
-            Delay = delay;
-            return this;
-        }
-
         protected override bool StartInternal() => true;
 
         public override string ToString() => $"Wait[S](seconds: {Delay.TotalSeconds})";
index c5e8255c5615c675a5e6196dea34ce5bf646ea38..b96ed5bec85489d245ec31b97cb5fe91000388ad 100644 (file)
@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Questionable.Model;
 using Questionable.Model.Questing;
@@ -9,18 +10,19 @@ namespace Questionable.Controller.Steps;
 
 internal sealed class TaskCreator
 {
-    private readonly IReadOnlyList<ITaskFactory> _taskFactories;
+    private readonly IServiceProvider _serviceProvider;
     private readonly ILogger<TaskCreator> _logger;
 
-    public TaskCreator(IEnumerable<ITaskFactory> taskFactories, ILogger<TaskCreator> logger)
+    public TaskCreator(IServiceProvider serviceProvider, ILogger<TaskCreator> logger)
     {
-        _taskFactories = taskFactories.ToList().AsReadOnly();
+        _serviceProvider = serviceProvider;
         _logger = logger;
     }
 
     public IReadOnlyList<ITask> CreateTasks(Quest quest, QuestSequence sequence, QuestStep step)
     {
-        var newTasks = _taskFactories
+        using var scope = _serviceProvider.CreateScope();
+        var newTasks = scope.ServiceProvider.GetRequiredService<IEnumerable<ITaskFactory>>()
             .SelectMany(x =>
             {
                 IList<ITask> tasks = x.CreateAllTasks(quest, sequence, step).ToList();
index 4c7f05b7386081beb57edac12a9ba7b5d3523dee..db3510cf5dde6631bddea4b22b00cc0c1b494f5d 100644 (file)
@@ -125,56 +125,42 @@ public sealed class QuestionablePlugin : IDalamudPlugin
     private static void AddTaskFactories(ServiceCollection serviceCollection)
     {
         // individual tasks
-        serviceCollection.AddTransient<MountTask>();
-        serviceCollection.AddTransient<UnmountTask>();
         serviceCollection.AddTransient<MoveToLandingLocation>();
         serviceCollection.AddTransient<DoGather>();
         serviceCollection.AddTransient<DoGatherCollectable>();
         serviceCollection.AddTransient<SwitchClassJob>();
+        serviceCollection.AddSingleton<Mount.Factory>();
 
         // task factories
-        serviceCollection.AddTaskWithFactory<StepDisabled.Factory, StepDisabled.Task>();
-        serviceCollection.AddSingleton<ITaskFactory, EquipRecommended.BeforeDutyOrInstance>();
-        serviceCollection.AddTaskWithFactory<GatheringRequiredItems.Factory, GatheringRequiredItems.StartGathering, GatheringRequiredItems.SkipMarker>();
-        serviceCollection.AddTaskWithFactory<AetheryteShortcut.Factory, AetheryteShortcut.UseAetheryteShortcut>();
-        serviceCollection.AddTaskWithFactory<SkipCondition.Factory, SkipCondition.CheckSkip>();
-        serviceCollection.AddTaskWithFactory<AethernetShortcut.Factory, AethernetShortcut.UseAethernetShortcut>();
-        serviceCollection.AddTaskWithFactory<WaitAtStart.Factory, WaitAtStart.WaitDelay>();
-        serviceCollection.AddTaskWithFactory<Move.Factory, Move.MoveInternal, Move.ExpectToBeNearDataId, Move.Land>();
-        serviceCollection.AddTransient<Move.MoveBuilder>();
-
-        serviceCollection.AddTaskWithFactory<NextQuest.Factory, NextQuest.SetQuest>();
-        serviceCollection.AddTaskWithFactory<AetherCurrent.Factory, AetherCurrent.DoAttune>();
-        serviceCollection.AddTaskWithFactory<AethernetShard.Factory, AethernetShard.DoAttune>();
-        serviceCollection.AddTaskWithFactory<Aetheryte.Factory, Aetheryte.DoAttune>();
-        serviceCollection.AddTaskWithFactory<Combat.Factory, Combat.HandleCombat>();
-        serviceCollection.AddTaskWithFactory<Duty.Factory, Duty.OpenDutyFinder>();
-        serviceCollection.AddTaskWithFactory<Emote.Factory, Emote.UseOnObject, Emote.Use>();
-        serviceCollection.AddTaskWithFactory<Action.Factory, Action.UseOnObject>();
-        serviceCollection.AddTaskWithFactory<Interact.Factory, Interact.DoInteract>();
-        serviceCollection.AddTaskWithFactory<Jump.Factory, Jump.SingleJump, Jump.RepeatedJumps>();
-        serviceCollection.AddTaskWithFactory<Dive.Factory, Dive.DoDive>();
-        serviceCollection.AddTaskWithFactory<Say.Factory, Say.UseChat>();
-        serviceCollection
-            .AddTaskWithFactory<UseItem.Factory, UseItem.UseOnGround, UseItem.UseOnObject, UseItem.Use,
-                UseItem.UseOnPosition>();
-        serviceCollection.AddTaskWithFactory<EquipItem.Factory, EquipItem.DoEquip>();
-        serviceCollection.AddTaskWithFactory<EquipRecommended.Factory, EquipRecommended.DoEquipRecommended>();
-        serviceCollection.AddTaskWithFactory<Craft.Factory, Craft.DoCraft>();
-        serviceCollection.AddTaskWithFactory<TurnInDelivery.Factory, TurnInDelivery.SatisfactionSupplyTurnIn>();
-        serviceCollection
-            .AddTaskWithFactory<InitiateLeve.Factory,
-                InitiateLeve.SkipInitiateIfActive,
-                InitiateLeve.OpenJournal,
-                InitiateLeve.Initiate,
-                InitiateLeve.SelectDifficulty>();
-
-        serviceCollection
-            .AddTaskWithFactory<WaitAtEnd.Factory,
-                WaitAtEnd.WaitDelay,
-                WaitAtEnd.WaitNextStepOrSequence,
-                WaitAtEnd.WaitForCompletionFlags,
-                WaitAtEnd.WaitObjectAtPosition>();
+        serviceCollection.AddTaskFactory<StepDisabled.Factory>();
+        serviceCollection.AddTaskFactory<EquipRecommended.BeforeDutyOrInstance>();
+        serviceCollection.AddTaskFactory<GatheringRequiredItems.Factory>();
+        serviceCollection.AddTaskFactory<AetheryteShortcut.Factory>();
+        serviceCollection.AddTaskFactory<SkipCondition.Factory>();
+        serviceCollection.AddTaskFactory<AethernetShortcut.Factory>();
+        serviceCollection.AddTaskFactory<WaitAtStart.Factory>();
+        serviceCollection.AddTaskFactory<MoveTo.Factory>();
+
+        serviceCollection.AddTaskFactory<NextQuest.Factory>();
+        serviceCollection.AddTaskFactory<AetherCurrent.Factory>();
+        serviceCollection.AddTaskFactory<AethernetShard.Factory>();
+        serviceCollection.AddTaskFactory<Aetheryte.Factory>();
+        serviceCollection.AddTaskFactory<Combat.Factory>();
+        serviceCollection.AddTaskFactory<Duty.Factory>();
+        serviceCollection.AddTaskFactory<Emote.Factory>();
+        serviceCollection.AddTaskFactory<Action.Factory>();
+        serviceCollection.AddTaskFactory<Interact.Factory>();
+        serviceCollection.AddTaskFactory<Jump.Factory>();
+        serviceCollection.AddTaskFactory<Dive.Factory>();
+        serviceCollection.AddTaskFactory<Say.Factory>();
+        serviceCollection.AddTaskFactory<UseItem.Factory>();
+        serviceCollection.AddTaskFactory<EquipItem.Factory>();
+        serviceCollection.AddTaskFactory<EquipRecommended.Factory>();
+        serviceCollection.AddTaskFactory<Craft.Factory>();
+        serviceCollection.AddTaskFactory<TurnInDelivery.Factory>();
+        serviceCollection.AddTaskFactory<InitiateLeve.Factory>();
+
+        serviceCollection.AddTaskFactory<WaitAtEnd.Factory>();
         serviceCollection.AddTransient<WaitAtEnd.WaitQuestAccepted>();
         serviceCollection.AddTransient<WaitAtEnd.WaitQuestCompleted>();
 
index a97401f2f6a08c114a3ad45f6754dfb89f7b7011..0247cefe742cbefae5b7ef28b7a64e7a6ed5bed2 100644 (file)
@@ -6,79 +6,12 @@ namespace Questionable;
 
 internal static class ServiceCollectionExtensions
 {
-    public static void AddTaskWithFactory<
-        [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
-        TFactory,
-        [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
-        TTask>(
+    public static void AddTaskFactory<
+        [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] TFactory>(
         this IServiceCollection serviceCollection)
         where TFactory : class, ITaskFactory
-        where TTask : class, ITask
     {
         serviceCollection.AddSingleton<ITaskFactory, TFactory>();
-        serviceCollection.AddTransient<TTask>();
-    }
-
-    public static void AddTaskWithFactory<
-        [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
-        TFactory,
-        [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
-        TTask1,
-        [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
-        TTask2>(
-        this IServiceCollection serviceCollection)
-        where TFactory : class, ITaskFactory
-        where TTask1 : class, ITask
-        where TTask2 : class, ITask
-    {
-        serviceCollection.AddSingleton<ITaskFactory, TFactory>();
-        serviceCollection.AddTransient<TTask1>();
-        serviceCollection.AddTransient<TTask2>();
-    }
-
-    public static void AddTaskWithFactory<
-        [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
-        TFactory,
-        [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
-        TTask1,
-        [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
-        TTask2,
-        [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
-        TTask3>(
-        this IServiceCollection serviceCollection)
-        where TFactory : class, ITaskFactory
-        where TTask1 : class, ITask
-        where TTask2 : class, ITask
-        where TTask3 : class, ITask
-    {
-        serviceCollection.AddSingleton<ITaskFactory, TFactory>();
-        serviceCollection.AddTransient<TTask1>();
-        serviceCollection.AddTransient<TTask2>();
-        serviceCollection.AddTransient<TTask3>();
-    }
-
-    public static void AddTaskWithFactory<
-        [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
-        TFactory,
-        [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
-        TTask1,
-        [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
-        TTask2,
-        [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
-        TTask3,
-        [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
-        TTask4>(
-        this IServiceCollection serviceCollection)
-        where TFactory : class, ITaskFactory
-        where TTask1 : class, ITask
-        where TTask2 : class, ITask
-        where TTask3 : class, ITask
-        where TTask4 : class, ITask
-    {
-        serviceCollection.AddSingleton<ITaskFactory, TFactory>();
-        serviceCollection.AddTransient<TTask1>();
-        serviceCollection.AddTransient<TTask2>();
-        serviceCollection.AddTransient<TTask3>();
-        serviceCollection.AddTransient<TTask4>();
+        serviceCollection.AddSingleton<TFactory>();
     }
 }