240 lines
6.4 KiB
C#
240 lines
6.4 KiB
C#
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<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
|
{
|
|
if (step.InteractionType != EInteractionType.MeldMateria)
|
|
{
|
|
return Array.Empty<ITask>();
|
|
}
|
|
ArgumentNullException.ThrowIfNull(step.ItemId, "step.ItemId");
|
|
return new global::_003C_003Ez__ReadOnlySingleElementList<ITask>(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<DoMeld> logger) : TaskExecutor<MeldTask>()
|
|
{
|
|
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<InventoryType> 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;
|
|
}
|
|
}
|
|
}
|