using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using LLib; using LLib.Inventory; using Lumina.Excel.Sheets; using Microsoft.Extensions.Logging; using Questionable.Functions; using Questionable.Model; using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Interactions; internal static class EquipItem { internal sealed class Factory : SimpleTaskFactory { public override ITask? CreateTask(Questionable.Model.Quest quest, QuestSequence sequence, QuestStep step) { if (step.InteractionType != EInteractionType.EquipItem) { return null; } ArgumentNullException.ThrowIfNull(step.ItemId, "step.ItemId"); return new Task(step.ItemId.Value); } } internal sealed record Task(uint ItemId) : ITask { public override string ToString() { return $"Equip({ItemId})"; } } internal sealed class DoEquip(IDataManager dataManager, ILogger logger) : TaskExecutor(), IToastAware, ITaskExecutor { private const int MaxAttempts = 3; private static readonly IReadOnlyList SourceInventoryTypes = new global::_003C_003Ez__ReadOnlyArray(new InventoryType[16] { InventoryType.ArmoryMainHand, InventoryType.ArmoryOffHand, InventoryType.ArmoryHead, InventoryType.ArmoryBody, InventoryType.ArmoryHands, InventoryType.ArmoryLegs, InventoryType.ArmoryFeets, InventoryType.ArmoryEar, InventoryType.ArmoryNeck, InventoryType.ArmoryWrist, InventoryType.ArmoryRings, InventoryType.ArmorySoulCrystal, InventoryType.Inventory1, InventoryType.Inventory2, InventoryType.Inventory3, InventoryType.Inventory4 }); private int _attempts; private Item? _item; private List _targetSlots; private long _continueAt = long.MaxValue; protected override bool Start() { _item = ((ItemHandle)base.Task.ItemId).GetRow(dataManager) ?? throw new ArgumentOutOfRangeException("ItemId"); _targetSlots = GetEquipSlot(_item) ?? throw new InvalidOperationException("Not a piece of equipment"); Equip(); _continueAt = Environment.TickCount64 + 1000; return true; } public override ETaskResult Update() { if (Environment.TickCount64 < _continueAt) { return ETaskResult.StillRunning; } if (InventoryHelper.IsItemEquipped(base.Task.ItemId)) { return ETaskResult.TaskComplete; } Equip(); _continueAt = Environment.TickCount64 + 1000; return ETaskResult.StillRunning; } private unsafe void Equip() { _attempts++; if (_attempts > 3) { throw new TaskException("Unable to equip gear."); } InventoryManager* inventoryManager = InventoryManager.Instance(); if (inventoryManager == null) { return; } if (InventoryHelper.IsItemEquipped(base.Task.ItemId)) { logger.LogDebug("Already equipped {Item}, skipping step", _item?.Name.ToString()); return; } foreach (InventoryType sourceInventoryType in SourceInventoryTypes) { InventoryContainer* inventoryContainer = inventoryManager->GetInventoryContainer(sourceInventoryType); if (inventoryContainer == null) { continue; } for (ushort num = 0; num < inventoryContainer->Size; num++) { InventoryItem* inventorySlot = inventoryContainer->GetInventorySlot(num); if (inventorySlot != null && inventorySlot->ItemId == base.Task.ItemId) { ushort num2 = _targetSlots.Where(delegate(ushort x) { InventoryItem* inventorySlot2 = inventoryManager->GetInventorySlot(InventoryType.EquippedItems, x); return inventorySlot2 == null || inventorySlot2->ItemId == 0; }).Concat(_targetSlots).First(); logger.LogDebug("Equipping item from {SourceInventory}, {SourceSlot} to {TargetInventory}, {TargetSlot}", sourceInventoryType, num, InventoryType.EquippedItems, num2); int num3 = inventoryManager->MoveItemSlot(sourceInventoryType, num, InventoryType.EquippedItems, num2, a6: true); logger.LogDebug("MoveItemSlot result: {Result}", num3); return; } } } throw new TaskException($"Could not equip item {base.Task.ItemId}."); } private static List? GetEquipSlot(Item? item) { if (!item.HasValue) { return null; } switch (item.Value.EquipSlotCategory.RowId) { case 1u: case 2u: case 3u: case 4u: case 5u: case 6u: case 7u: case 8u: case 9u: case 10u: case 11u: { int num = 1; List list4 = new List(num); CollectionsMarshal.SetCount(list4, num); CollectionsMarshal.AsSpan(list4)[0] = (ushort)(item.Value.EquipSlotCategory.RowId - 1); return list4; } case 12u: { int num = 2; List list3 = new List(num); CollectionsMarshal.SetCount(list3, num); Span span = CollectionsMarshal.AsSpan(list3); span[0] = 11; span[1] = 12; return list3; } case 13u: { int num = 1; List list2 = new List(num); CollectionsMarshal.SetCount(list2, num); CollectionsMarshal.AsSpan(list2)[0] = 0; return list2; } case 17u: { int num = 1; List list = new List(num); CollectionsMarshal.SetCount(list, num); CollectionsMarshal.AsSpan(list)[0] = 13; return list; } default: return null; } } public bool OnErrorToast(SeString message) { string b = dataManager.GetString(709u, (LogMessage x) => x.Text); if (GameFunctions.GameStringEquals(message.TextValue, b)) { _attempts = 3; } return false; } public override bool ShouldInterruptOnDamage() { return true; } } }