using System; using System.Collections.Generic; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using LLib; using LLib.Gear; using Microsoft.Extensions.Logging; using Questionable.Model; using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Interactions; internal static class MeldMateria { internal sealed class Factory : ITaskFactory { public IEnumerable CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { if (step.InteractionType != EInteractionType.MeldMateria) { return Array.Empty(); } ArgumentNullException.ThrowIfNull(step.ItemId, "step.ItemId"); return new global::_003C_003Ez__ReadOnlySingleElementList(new MeldTask(step.ItemId.Value, step.DataId)); } } internal sealed record MeldTask(uint MateriaItemId, uint? TargetItemId) : ITask { public override string ToString() { return $"Meld materia {MateriaItemId}" + (TargetItemId.HasValue ? $" into item {TargetItemId}" : ""); } } internal sealed class DoMeld(IGameGui gameGui, ILogger logger) : TaskExecutor() { private enum EMeldState { OpeningAgent, WaitingForAgent, SelectingCategory, WaitingAfterCategory, SelectingItem, WaitingAfterItem, SelectingMateria, WaitingForDialog, Confirming, WaitingForCompletion } private Throttle _throttle; private int _materiaStartCount; private unsafe InventoryItem* _targetItem; private InventoryType _targetInventoryType; private EMeldState _state; private long _stateChangedAt; public override bool HasTimedOut(float timeoutSeconds) { return Environment.TickCount64 - _stateChangedAt > (long)(timeoutSeconds * 1000f); } public override void ResetTimeout() { base.ResetTimeout(); _stateChangedAt = Environment.TickCount64; } private void SetState(EMeldState state) { _state = state; _stateChangedAt = Environment.TickCount64; } protected override bool Start() { _materiaStartCount = MateriaHelper.CountMateriaInInventory(base.Task.MateriaItemId); if (_materiaStartCount == 0) { logger.LogInformation("Already used materia {MateriaId} or don't have it", base.Task.MateriaItemId); return false; } if (!FindTargetItem()) { throw new TaskException("Cannot find target item to meld materia into"); } SetState(EMeldState.OpeningAgent); return true; } public unsafe override ETaskResult Update() { if (!_throttle.TryReset(0.5)) { return ETaskResult.StillRunning; } int num = MateriaHelper.CountMateriaInInventory(base.Task.MateriaItemId); if (num < _materiaStartCount) { logger.LogInformation("Materia {MateriaId} melded successfully ({Old} -> {New})", base.Task.MateriaItemId, _materiaStartCount, num); MeldActions.CloseMeldAgent(); return ETaskResult.TaskComplete; } switch (_state) { case EMeldState.OpeningAgent: if (MeldActions.TryOpenMeldAgent()) { SetState(EMeldState.WaitingForAgent); } break; case EMeldState.WaitingForAgent: if (MeldActions.IsMeldAgentReady()) { SetState(EMeldState.SelectingCategory); } break; case EMeldState.SelectingCategory: if (MeldActions.TrySelectItemCategory((int)MeldActions.GetFilterCategory(_targetInventoryType))) { SetState(EMeldState.WaitingAfterCategory); } break; case EMeldState.WaitingAfterCategory: if (!MeldActions.IsAgentLoading()) { SetState(EMeldState.SelectingItem); } break; case EMeldState.SelectingItem: { int? num2 = MeldActions.FindItemIndex(_targetItem); if (!num2.HasValue) { logger.LogWarning("Could not find target item in agent list"); } else if (MeldActions.TrySelectItem(num2.Value)) { logger.LogInformation("Selected item at agent index {Index}", num2.Value); SetState(EMeldState.WaitingAfterItem); } break; } case EMeldState.WaitingAfterItem: if (!MeldActions.IsAgentLoading()) { SetState(EMeldState.SelectingMateria); } break; case EMeldState.SelectingMateria: { int? num3 = MeldActions.FindMateriaIndex(base.Task.MateriaItemId); if (!num3.HasValue) { logger.LogWarning("Could not find materia {MateriaId} in agent list", base.Task.MateriaItemId); } else if (MeldActions.TrySelectMateria(num3.Value)) { logger.LogInformation("Selected materia at agent index {Index}", num3.Value); SetState(EMeldState.WaitingForDialog); } break; } case EMeldState.WaitingForDialog: if (MeldActions.IsMeldDialogReady(gameGui)) { SetState(EMeldState.Confirming); } break; case EMeldState.Confirming: if (MeldActions.TryConfirmMeld(gameGui)) { logger.LogInformation("Confirmed meld"); SetState(EMeldState.WaitingForCompletion); } break; } return ETaskResult.StillRunning; } private unsafe bool FindTargetItem() { InventoryManager* ptr = InventoryManager.Instance(); if (ptr == null) { return false; } ReadOnlySpan readOnlySpan = new InventoryType[15] { InventoryType.Inventory1, InventoryType.Inventory2, InventoryType.Inventory3, InventoryType.Inventory4, InventoryType.ArmoryMainHand, InventoryType.ArmoryOffHand, InventoryType.ArmoryHead, InventoryType.ArmoryBody, InventoryType.ArmoryHands, InventoryType.ArmoryLegs, InventoryType.ArmoryFeets, InventoryType.ArmoryEar, InventoryType.ArmoryNeck, InventoryType.ArmoryWrist, InventoryType.ArmoryRings }; for (int i = 0; i < readOnlySpan.Length; i++) { InventoryType inventoryType = readOnlySpan[i]; InventoryContainer* inventoryContainer = ptr->GetInventoryContainer(inventoryType); if (inventoryContainer == null) { continue; } for (int j = 0; j < inventoryContainer->Size; j++) { InventoryItem* inventorySlot = inventoryContainer->GetInventorySlot(j); if (inventorySlot != null && inventorySlot->ItemId != 0 && (!base.Task.TargetItemId.HasValue || inventorySlot->ItemId == base.Task.TargetItemId.Value)) { _targetItem = inventorySlot; _targetInventoryType = inventoryType; logger.LogInformation("Found target item {ItemId} in {InvType} slot {Slot}", inventorySlot->ItemId, inventoryType, j); return true; } } } return false; } public override bool ShouldInterruptOnDamage() { return false; } } }