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 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 DateTime _continueAt = DateTime.MaxValue; protected override bool Start() { _item = dataManager.GetExcelSheet().GetRowOrDefault(base.Task.ItemId) ?? throw new ArgumentOutOfRangeException("ItemId"); _targetSlots = GetEquipSlot(_item) ?? throw new InvalidOperationException("Not a piece of equipment"); Equip(); _continueAt = DateTime.Now.AddSeconds(1.0); return true; } public unsafe override ETaskResult Update() { if (DateTime.Now < _continueAt) { return ETaskResult.StillRunning; } InventoryManager* ptr = InventoryManager.Instance(); if (ptr == null) { return ETaskResult.StillRunning; } foreach (ushort targetSlot in _targetSlots) { InventoryItem* inventorySlot = ptr->GetInventorySlot(InventoryType.EquippedItems, targetSlot); if (inventorySlot != null && inventorySlot->ItemId == base.Task.ItemId) { return ETaskResult.TaskComplete; } } Equip(); _continueAt = DateTime.Now.AddSeconds(1.0); 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; } InventoryContainer* inventoryContainer = inventoryManager->GetInventoryContainer(InventoryType.EquippedItems); if (inventoryContainer == null) { return; } foreach (ushort targetSlot in _targetSlots) { InventoryItem* inventorySlot = inventoryContainer->GetInventorySlot(targetSlot); if (inventorySlot != null && inventorySlot->ItemId == base.Task.ItemId) { logger.LogInformation("Already equipped {Item}, skipping step", _item?.Name.ToString()); return; } } foreach (InventoryType sourceInventoryType in SourceInventoryTypes) { InventoryContainer* inventoryContainer2 = inventoryManager->GetInventoryContainer(sourceInventoryType); if (inventoryContainer2 == null || (inventoryManager->GetItemCountInContainer(base.Task.ItemId, sourceInventoryType, isHq: true, 0) == 0 && inventoryManager->GetItemCountInContainer(base.Task.ItemId, sourceInventoryType, isHq: false, 0) == 0)) { continue; } for (ushort num = 0; num < inventoryContainer2->Size; num++) { InventoryItem* inventorySlot2 = inventoryContainer2->GetInventorySlot(num); if (inventorySlot2 != null && inventorySlot2->ItemId == base.Task.ItemId) { ushort num2 = _targetSlots.Where(delegate(ushort x) { InventoryItem* inventorySlot3 = inventoryManager->GetInventorySlot(InventoryType.EquippedItems, x); return inventorySlot3 == null || inventorySlot3->ItemId == 0; }).Concat(_targetSlots).First(); logger.LogInformation("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.LogInformation("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 new List(); } Span span; 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 index = 1; List list4 = new List(index); CollectionsMarshal.SetCount(list4, index); span = CollectionsMarshal.AsSpan(list4); int num = 0; span[num] = (ushort)(item.Value.EquipSlotCategory.RowId - 1); return list4; } case 12u: { int num = 2; List list3 = new List(num); CollectionsMarshal.SetCount(list3, num); span = CollectionsMarshal.AsSpan(list3); int index = 0; span[index] = 11; index++; span[index] = 12; return list3; } case 13u: { int index = 1; List list2 = new List(index); CollectionsMarshal.SetCount(list2, index); span = CollectionsMarshal.AsSpan(list2); int num = 0; span[num] = 0; return list2; } case 17u: { int num = 1; List list = new List(num); CollectionsMarshal.SetCount(list, num); span = CollectionsMarshal.AsSpan(list); int index = 0; span[index] = 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; } } }