"Z": -273.68756
           },
           "TerritoryId": 959,
-          "InteractionType": "WalkTo"
+          "InteractionType": "WalkTo",
+          "Fly": true
         },
         {
           "DataId": 1044403,
 
 
 internal static class NextQuest
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
     {
-        public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
+        public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (step.InteractionType != EInteractionType.CompleteQuest)
                 return null;
 
 
 internal static class TurnInDelivery
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
     {
-        public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
+        public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (quest.Id is not SatisfactionSupplyNpcId || sequence.Sequence != 1)
                 return null;
 
 
 internal interface ITaskFactory
 {
-    ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step);
-
-    IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
-    {
-        var task = CreateTask(quest, sequence, step);
-        if (task != null)
-            yield return task;
-    }
+    IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step);
 }
 
                 return [unmount, task];
             }
         }
-
-        public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
-            => throw new InvalidOperationException();
     }
 
     internal sealed class UseOnObject(GameFunctions gameFunctions, ILogger<UseOnObject> logger) : ITask
 
 
 internal static class AetherCurrent
 {
-    internal sealed class Factory(IServiceProvider serviceProvider, AetherCurrentData aetherCurrentData, IChatGui chatGui) : ITaskFactory
+    internal sealed class Factory(IServiceProvider serviceProvider, AetherCurrentData aetherCurrentData, IChatGui chatGui) : SimpleTaskFactory
     {
-        public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
+        public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (step.InteractionType != EInteractionType.AttuneAetherCurrent)
                 return null;
 
 
 internal static class AethernetShard
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
     {
-        public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
+        public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (step.InteractionType != EInteractionType.AttuneAethernetShard)
                 return null;
 
 
 internal static class Aetheryte
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
     {
-        public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
+        public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (step.InteractionType != EInteractionType.AttuneAetheryte)
                 return null;
 
 
 internal static class Dive
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
     {
-        public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
+        public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (step.InteractionType != EInteractionType.Dive)
                 return null;
 
 
 internal static class Duty
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
     {
-        public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
+        public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (step.InteractionType != EInteractionType.Duty)
                 return null;
 
                 return [unmount, task];
             }
         }
-
-        public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
-            => throw new InvalidOperationException();
     }
 
     internal sealed class UseOnObject(ChatFunctions chatFunctions) : AbstractDelayedTask
 
 
 internal static class EquipItem
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
     {
-        public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
+        public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (step.InteractionType != EInteractionType.EquipItem)
                 return null;
 
 
 internal static class EquipRecommended
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
     {
-        public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
+        public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (step.InteractionType != EInteractionType.EquipRecommended)
                 return null;
         }
     }
 
-    internal sealed class BeforeDutyOrInstance(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class BeforeDutyOrInstance(IServiceProvider serviceProvider) : SimpleTaskFactory
     {
-        public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
+        public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (step.InteractionType != EInteractionType.Duty &&
                 step.InteractionType != EInteractionType.SinglePlayerDuty &&
 
                 .With(step.DataId.Value, quest, step.InteractionType,
                     step.TargetTerritoryId != null || quest.Id is SatisfactionSupplyNpcId);
         }
-
-        public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
-            => throw new InvalidOperationException();
     }
 
     internal sealed class DoInteract(GameFunctions gameFunctions, ICondition condition, ILogger<DoInteract> logger)
 
 
 internal static class Jump
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
     {
-        public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
+        public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (step.InteractionType != EInteractionType.Jump)
                 return null;
 
             var task = serviceProvider.GetRequiredService<UseChat>().With(excelString);
             return [unmount, task];
         }
-
-        public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
-            => throw new InvalidOperationException();
     }
 
     internal sealed class UseChat(ChatFunctions chatFunctions) : AbstractDelayedTask
 
             }
         }
 
-        public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
-            => throw new InvalidOperationException();
-
         private IEnumerable<ITask> CreateVesperBayFallbackTask()
         {
             logger.LogWarning("No vesper bay aetheryte tickets in inventory, navigating via ferry in Limsa instead");
 
             yield return serviceProvider.GetRequiredService<SelectDifficulty>();
             yield return new WaitConditionTask(() => condition[ConditionFlag.BoundByDuty], "Wait(BoundByDuty)");
         }
-
-        public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
-            => throw new NotImplementedException();
     }
 
     internal sealed unsafe class SkipInitiateIfActive : ITask
 
 
 internal static class AethernetShortcut
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
     {
-        public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
+        public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (step.AethernetShortcut == null)
                 return null;
 
 {
     internal sealed class Factory(
         IServiceProvider serviceProvider,
-        AetheryteData aetheryteData) : ITaskFactory
+        AetheryteData aetheryteData) : SimpleTaskFactory
     {
-        public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
+        public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (step.AetheryteShortcut == null)
                 return null;
 
 using System;
+using System.Collections.Generic;
 using System.Linq;
 using Dalamud.Plugin.Services;
+using FFXIVClientStructs.FFXIV.Client.Game;
+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 Quest = Questionable.Model.Quest;
 {
     internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
     {
-        public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
+        public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (step.InteractionType != EInteractionType.Craft)
-                return null;
+                return [];
 
             ArgumentNullException.ThrowIfNull(step.ItemId);
             ArgumentNullException.ThrowIfNull(step.ItemCount);
-            return serviceProvider.GetRequiredService<DoCraft>()
-                .With(step.ItemId.Value, step.ItemCount.Value);
+            return
+            [
+                serviceProvider.GetRequiredService<UnmountTask>(),
+                serviceProvider.GetRequiredService<DoCraft>()
+                    .With(step.ItemId.Value, step.ItemCount.Value)
+            ];
         }
     }
 
-    internal sealed class DoCraft(IDataManager dataManager, IClientState clientState, ArtisanIpc artisanIpc) : ITask
+    internal sealed class DoCraft(
+        IDataManager dataManager,
+        IClientState clientState,
+        ArtisanIpc artisanIpc,
+        ILogger<DoCraft> logger) : ITask
     {
         private uint _itemId;
         private int _itemCount;
 
         public bool Start()
         {
+            if (HasRequestedItems())
+            {
+                logger.LogInformation("Already own {ItemCount}x {ItemId}", _itemCount, _itemId);
+                return false;
+            }
+
             RecipeLookup? recipeLookup = dataManager.GetExcelSheet<RecipeLookup>()!.GetRow(_itemId);
             if (recipeLookup == null)
                 throw new TaskException($"Item {_itemId} is not craftable");
 
-            uint recipeId = ((EClassJob)clientState.LocalPlayer!.ClassJob.Id) switch
+            uint recipeId = (EClassJob)clientState.LocalPlayer!.ClassJob.Id switch
             {
                 EClassJob.Carpenter => recipeLookup.CRP.Row,
                 EClassJob.Blacksmith => recipeLookup.BSM.Row,
 
             if (recipeId == 0)
             {
-                recipeId = new[]{
+                recipeId = new[]
+                    {
                         recipeLookup.CRP.Row,
                         recipeLookup.BSM.Row,
                         recipeLookup.ARM.Row,
             if (recipeId == 0)
                 throw new TaskException($"Unable to determine recipe for item {_itemId}");
 
-            if (!artisanIpc.CraftItem((ushort)recipeId, _itemCount))
+            int remainingItemCount = _itemCount - GetOwnedItemCount();
+            logger.LogInformation(
+                "Starting craft for item {ItemId} with recipe {RecipeId} for {RemainingItemCount} items",
+                _itemId, recipeId, remainingItemCount);
+            if (!artisanIpc.CraftItem((ushort)recipeId, remainingItemCount))
                 throw new TaskException($"Failed to start Artisan craft for recipe {recipeId}");
 
             return true;
         }
 
-        public ETaskResult Update()
+        public unsafe ETaskResult Update()
         {
+            if (HasRequestedItems() && !artisanIpc.IsCrafting())
+            {
+                AgentRecipeNote* agentRecipeNote = AgentRecipeNote.Instance();
+                if (agentRecipeNote != null && agentRecipeNote->IsAgentActive())
+                {
+                    uint addonId = agentRecipeNote->GetAddonId();
+                    if (addonId == 0)
+                        return ETaskResult.StillRunning;
+
+                    AtkUnitBase* addon = AtkStage.Instance()->RaptureAtkUnitManager->GetAddonById((ushort)addonId);
+                    if (addon != null)
+                    {
+                        logger.LogInformation("Closing crafting window");
+                        addon->Close(true);
+                        return ETaskResult.TaskComplete;
+                    }
+                }
+            }
+
             return ETaskResult.StillRunning;
         }
+
+        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);
+        }
+
+        public override string ToString() => $"Craft {_itemCount}x {_itemId} (with Artisan)";
     }
 }
 
                        minCollectability: (short)requiredGatheredItems.Collectability) >=
                    requiredGatheredItems.ItemCount;
         }
-
-        public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
-            => throw new NotImplementedException();
     }
 
     internal sealed class StartGathering(GatheringController gatheringController) : ITask
 
 
             return [];
         }
-
-        public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
-            => throw new InvalidOperationException();
     }
 
     internal sealed class MoveBuilder(
 
 
 internal static class SkipCondition
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
     {
-        public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
+        public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             var skipConditions = step.SkipConditions?.StepIf;
             if (skipConditions is { Never: true })
 
 
 internal static class StepDisabled
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
     {
-        public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
+        public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (!step.Disabled)
                 return null;
 
             }
         }
 
-        public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
-            => throw new InvalidOperationException();
-
         private static NextStep Next(Quest quest, QuestSequence sequence)
         {
             return new NextStep(quest.Id, sequence.Sequence);
 
 
 internal static class WaitAtStart
 {
-    internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+    internal sealed class Factory(IServiceProvider serviceProvider) : SimpleTaskFactory
     {
-        public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
+        public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
         {
             if (step.DelaySecondsAtStart == null)
                 return null;
 
--- /dev/null
+using System.Collections.Generic;
+using Questionable.Model;
+using Questionable.Model.Questing;
+
+namespace Questionable.Controller.Steps;
+
+internal abstract class SimpleTaskFactory : ITaskFactory
+{
+    public abstract ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step);
+
+    public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
+    {
+        var task = CreateTask(quest, sequence, step);
+        if (task != null)
+            yield return task;
+    }
+}
 
 {
     private readonly ILogger<ArtisanIpc> _logger;
     private readonly ICallGateSubscriber<ushort, int, object> _craftItem;
+    private readonly ICallGateSubscriber<bool> _getEnduranceStatus;
 
     public ArtisanIpc(IDalamudPluginInterface pluginInterface, ILogger<ArtisanIpc> logger)
     {
         _logger = logger;
         _craftItem = pluginInterface.GetIpcSubscriber<ushort, int, object>("Artisan.CraftItem");
+        _getEnduranceStatus = pluginInterface.GetIpcSubscriber<bool>("Artisan.GetEnduranceStatus");
     }
 
     public bool CraftItem(ushort recipeId, int quantity)
             return false;
         }
     }
+
+    /// <summary>
+    /// This ignores crafting lists, but we can't create/use those.
+    /// </summary>
+    public bool IsCrafting()
+    {
+        try
+        {
+            return _getEnduranceStatus.InvokeFunc();
+        }
+        catch (IpcError e)
+        {
+            _logger.LogError(e, "Unable to check for Artisan endurance status");
+            return false;
+        }
+    }
 }
 
         if (IsLoadingScreenVisible())
             return true;
 
+        if (_condition[ConditionFlag.Crafting])
+        {
+            if (!AgentRecipeNote.Instance()->IsAgentActive())
+                return true;
+
+            if (!_condition[ConditionFlag.PreparingToCraft])
+                return true;
+        }
+
         return _condition[ConditionFlag.Occupied] || _condition[ConditionFlag.Occupied30] ||
                _condition[ConditionFlag.Occupied33] || _condition[ConditionFlag.Occupied38] ||
                _condition[ConditionFlag.Occupied39] || _condition[ConditionFlag.OccupiedInEvent] ||