"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] ||