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.Sheets; using Microsoft.Extensions.Logging; using Questionable.Controller.Steps.Common; using Questionable.External; using Questionable.Model; using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Shared; internal static class Craft { internal sealed class Factory : ITaskFactory { public IEnumerable CreateAllTasks(Questionable.Model.Quest quest, QuestSequence sequence, QuestStep step) { if (step.InteractionType != EInteractionType.Craft) { return Array.Empty(); } ArgumentNullException.ThrowIfNull(step.ItemId, "step.ItemId"); ArgumentNullException.ThrowIfNull(step.ItemCount, "step.ItemCount"); return new global::_003C_003Ez__ReadOnlyArray(new ITask[2] { new Questionable.Controller.Steps.Common.Mount.UnmountTask(), new CraftTask(step.ItemId.Value, step.ItemCount.Value) }); } } internal sealed record CraftTask(uint ItemId, int ItemCount) : ITask { public override string ToString() { return $"Craft {ItemCount}x {ItemId} (with Artisan)"; } } internal sealed class DoCraft(IDataManager dataManager, IClientState clientState, ArtisanIpc artisanIpc, ILogger logger) : TaskExecutor() { protected override bool Start() { if (HasRequestedItems()) { logger.LogInformation("Already own {ItemCount}x {ItemId}", base.Task.ItemCount, base.Task.ItemId); return false; } RecipeLookup? rowOrDefault = dataManager.GetExcelSheet().GetRowOrDefault(base.Task.ItemId); if (!rowOrDefault.HasValue) { throw new TaskException($"Item {base.Task.ItemId} is not craftable"); } uint num = (EClassJob)clientState.LocalPlayer.ClassJob.RowId switch { EClassJob.Carpenter => rowOrDefault.Value.CRP.RowId, EClassJob.Blacksmith => rowOrDefault.Value.BSM.RowId, EClassJob.Armorer => rowOrDefault.Value.ARM.RowId, EClassJob.Goldsmith => rowOrDefault.Value.GSM.RowId, EClassJob.Leatherworker => rowOrDefault.Value.LTW.RowId, EClassJob.Weaver => rowOrDefault.Value.WVR.RowId, EClassJob.Alchemist => rowOrDefault.Value.ALC.RowId, EClassJob.Culinarian => rowOrDefault.Value.CUL.RowId, _ => 0u, }; if (num == 0) { num = new uint[8] { rowOrDefault.Value.CRP.RowId, rowOrDefault.Value.BSM.RowId, rowOrDefault.Value.ARM.RowId, rowOrDefault.Value.GSM.RowId, rowOrDefault.Value.LTW.RowId, rowOrDefault.Value.WVR.RowId, rowOrDefault.Value.ALC.RowId, rowOrDefault.Value.WVR.RowId }.FirstOrDefault((uint x) => x != 0); } if (num == 0) { throw new TaskException($"Unable to determine recipe for item {base.Task.ItemId}"); } int num2 = base.Task.ItemCount - GetOwnedItemCount(); logger.LogInformation("Starting craft for item {ItemId} with recipe {RecipeId} for {RemainingItemCount} items", base.Task.ItemId, num, num2); if (!artisanIpc.CraftItem((ushort)num, num2)) { throw new TaskException($"Failed to start Artisan craft for recipe {num}"); } return true; } public unsafe override ETaskResult Update() { if (HasRequestedItems() && !artisanIpc.IsCrafting()) { AgentRecipeNote* ptr = AgentRecipeNote.Instance(); if (ptr != null && ptr->IsAgentActive()) { uint addonId = ptr->GetAddonId(); if (addonId == 0) { return ETaskResult.StillRunning; } AtkUnitBase* addonById = AtkStage.Instance()->RaptureAtkUnitManager->GetAddonById((ushort)addonId); if (addonById != null) { logger.LogInformation("Closing crafting window"); addonById->FireCallbackInt(-1); return ETaskResult.TaskComplete; } } } return ETaskResult.StillRunning; } private bool HasRequestedItems() { return GetOwnedItemCount() >= base.Task.ItemCount; } private unsafe int GetOwnedItemCount() { InventoryManager* ptr = InventoryManager.Instance(); return ptr->GetInventoryItemCount(base.Task.ItemId, isHq: false, checkEquipped: false, checkArmory: true, 0) + ptr->GetInventoryItemCount(base.Task.ItemId, isHq: true, checkEquipped: false, checkArmory: true, 0); } public override bool ShouldInterruptOnDamage() { return false; } } }