239 lines
6.7 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|