qstbak/Questionable/Questionable.Controller.Steps.Interactions/MeldMateria.cs
2026-08-19 13:19:57 +10:00

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;
}
}
}