qstbak/Questionable/Questionable.Controller.Steps.Interactions/EquipItem.cs
2025-10-09 07:47:19 +10:00

239 lines
6.7 KiB
C#

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<DoEquip> logger) : TaskExecutor<Task>(), IToastAware, ITaskExecutor
{
private const int MaxAttempts = 3;
private static readonly IReadOnlyList<InventoryType> SourceInventoryTypes = new global::_003C_003Ez__ReadOnlyArray<InventoryType>(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<ushort> _targetSlots;
private DateTime _continueAt = DateTime.MaxValue;
protected override bool Start()
{
_item = dataManager.GetExcelSheet<Item>().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<ushort>? GetEquipSlot(Item? item)
{
if (!item.HasValue)
{
return new List<ushort>();
}
Span<ushort> 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<ushort> list4 = new List<ushort>(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<ushort> list3 = new List<ushort>(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<ushort> list2 = new List<ushort>(index);
CollectionsMarshal.SetCount(list2, index);
span = CollectionsMarshal.AsSpan(list2);
int num = 0;
span[num] = 0;
return list2;
}
case 17u:
{
int num = 1;
List<ushort> list = new List<ushort>(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;
}
}
}