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

293 lines
10 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Common;
using Questionable.Controller.Steps.Movement;
using Questionable.Controller.Steps.Shared;
using Questionable.Controller.Utils;
using Questionable.Data;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Common;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Interactions;
internal static class UseItem
{
internal sealed class Factory(IClientState clientState, TerritoryData territoryData, ILogger<Factory> logger) : ITaskFactory
{
public unsafe IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
EInteractionType interactionType = step.InteractionType;
if ((interactionType == EInteractionType.SinglePlayerDuty || interactionType == EInteractionType.CompleteQuest) ? true : false)
{
if (!step.ItemId.HasValue)
{
return Array.Empty<ITask>();
}
}
else if (step.InteractionType != EInteractionType.UseItem)
{
return Array.Empty<ITask>();
}
ArgumentNullException.ThrowIfNull(step.ItemId, "step.ItemId");
if (step.ItemId == 30362)
{
if (InventoryManager.Instance()->GetInventoryItemCount(step.ItemId.Value, isHq: false, checkEquipped: true, checkArmory: true, 0) == 0)
{
return CreateVesperBayFallbackTask();
}
UseOnSelf useOnSelf = new UseOnSelf(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
int num = sequence.Steps.IndexOf(step);
Vector3? position = (sequence.Steps.Skip(num + 1).FirstOrDefault() ?? step).Position;
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[4]
{
useOnSelf,
new WaitCondition.Task(() => clientState.TerritoryType == 140, "Wait(territory: " + territoryData.GetNameAndId(140) + ")"),
new Mount.MountTask(140, position.HasValue ? Mount.EMountIf.AwayFromPosition : Mount.EMountIf.Always, position),
new MoveTask(140, new Vector3(-408.92343f, 23.167036f, -351.16223f), null, 0.25f, null, DisableNavmesh: true, false, Fly: false, Land: false, IgnoreDistanceToObject: false, RestartNavigation: true, EInteractionType.WalkTo)
});
}
Mount.UnmountTask unmountTask = new Mount.UnmountTask();
if (step.GroundTarget == true)
{
ITask task;
if (step.DataId.HasValue)
{
task = new UseOnGround(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
}
else
{
ArgumentNullException.ThrowIfNull(step.Position, "step.Position");
task = new UseOnPosition(quest.Id, step.Position.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
}
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[3]
{
unmountTask,
new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(0.5)),
task
});
}
if (step.DataId.HasValue)
{
UseOnObject useOnObject = new UseOnObject(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2] { unmountTask, useOnObject });
}
UseOnSelf useOnSelf2 = new UseOnSelf(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2] { unmountTask, useOnSelf2 });
}
private IEnumerable<ITask> CreateVesperBayFallbackTask()
{
logger.LogWarning("No vesper bay aetheryte tickets in inventory, navigating via ferry in Limsa instead");
uint npcId = 1003540u;
ushort territoryId = 129;
Vector3 destination = new Vector3(-360.9217f, 8f, 38.92566f);
yield return new AetheryteShortcut.Task(null, null, EAetheryteLocation.Limsa, territoryId);
yield return new Questionable.Controller.Steps.Shared.AethernetShortcut.Task(EAetheryteLocation.Limsa, EAetheryteLocation.LimsaArcanist);
yield return new WaitAtEnd.WaitDelay();
uint? dataId = npcId;
bool? sprint = false;
yield return new MoveTask(territoryId, destination, null, null, dataId, DisableNavmesh: false, sprint, Fly: false, Land: false, IgnoreDistanceToObject: false, RestartNavigation: true, EInteractionType.WalkTo);
yield return new Interact.Task(npcId, null, EInteractionType.None, SkipMarkerCheck: true);
}
}
internal interface IUseItemBase : ITask
{
ElementId? QuestId { get; }
uint ItemId { get; }
IList<QuestWorkValue?> CompletionQuestVariablesFlags { get; }
bool StartingCombat { get; }
}
internal abstract class UseItemExecutorBase<T>(QuestFunctions questFunctions, ICondition condition, ILogger logger) : TaskExecutor<T>() where T : class, IUseItemBase
{
private bool _usedItem;
private DateTime _continueAt;
private int _itemCount;
private ElementId? QuestId => base.Task.QuestId;
protected uint ItemId => base.Task.ItemId;
private IList<QuestWorkValue?> CompletionQuestVariablesFlags => base.Task.CompletionQuestVariablesFlags;
private bool StartingCombat => base.Task.StartingCombat;
protected abstract bool UseItem();
protected unsafe override bool Start()
{
InventoryManager* ptr = InventoryManager.Instance();
if (ptr == null)
{
throw new TaskException("No InventoryManager");
}
_itemCount = ptr->GetInventoryItemCount(ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0);
if (_itemCount == 0)
{
throw new TaskException($"Don't have any {ItemId} in inventory (checks NQ only)");
}
base.ProgressContext = InteractionProgressContext.FromActionUseOrDefault(() => _usedItem = UseItem());
_continueAt = DateTime.Now.Add(GetRetryDelay());
return true;
}
public unsafe override ETaskResult Update()
{
if (QuestId is QuestId elementId && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags))
{
QuestProgressInfo questProgressInfo = _003CquestFunctions_003EP.GetQuestProgressInfo(elementId);
if (questProgressInfo != null && QuestWorkUtils.MatchesQuestWork(CompletionQuestVariablesFlags, questProgressInfo))
{
return ETaskResult.TaskComplete;
}
}
if (DateTime.Now <= _continueAt)
{
return ETaskResult.StillRunning;
}
if (StartingCombat && _003Ccondition_003EP[ConditionFlag.InCombat])
{
return ETaskResult.TaskComplete;
}
if (ItemId == 30362 && _usedItem)
{
InventoryManager* ptr = InventoryManager.Instance();
if (ptr == null)
{
_003Clogger_003EP.LogWarning("InventoryManager is not available");
return ETaskResult.StillRunning;
}
if (ptr->GetInventoryItemCount(ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0) == _itemCount)
{
_003Clogger_003EP.LogInformation("Attempted to use vesper bay aetheryte ticket, but it didn't consume an item - reattempting next frame");
_usedItem = false;
return ETaskResult.StillRunning;
}
}
if (!_usedItem)
{
base.ProgressContext = InteractionProgressContext.FromActionUseOrDefault(() => _usedItem = UseItem());
_continueAt = DateTime.Now.Add(GetRetryDelay());
return ETaskResult.StillRunning;
}
return ETaskResult.TaskComplete;
}
private TimeSpan GetRetryDelay()
{
if (ItemId == 30362)
{
return TimeSpan.FromSeconds(11L);
}
return TimeSpan.FromSeconds(5L);
}
public override bool ShouldInterruptOnDamage()
{
return true;
}
}
internal sealed record UseOnGround(ElementId? QuestId, uint DataId, uint ItemId, IList<QuestWorkValue?> CompletionQuestVariablesFlags, bool StartingCombat = false) : IUseItemBase, ITask
{
public override string ToString()
{
return $"UseItem({ItemId} on ground at {DataId})";
}
}
internal sealed class UseOnGroundExecutor : UseItemExecutorBase<UseOnGround>
{
public UseOnGroundExecutor(GameFunctions gameFunctions, QuestFunctions questFunctions, ICondition condition, ILogger<UseOnGroundExecutor> logger)
{
_003CgameFunctions_003EP = gameFunctions;
base._002Ector(questFunctions, condition, (ILogger)logger);
}
protected override bool UseItem()
{
return _003CgameFunctions_003EP.UseItemOnGround(base.Task.DataId, base.ItemId);
}
}
internal sealed record UseOnPosition(ElementId? QuestId, Vector3 Position, uint ItemId, IList<QuestWorkValue?> CompletionQuestVariablesFlags, bool StartingCombat = false) : IUseItemBase, ITask
{
public override string ToString()
{
return $"UseItem({ItemId} on ground at {Position.ToString("G", CultureInfo.InvariantCulture)})";
}
}
internal sealed class UseOnPositionExecutor : UseItemExecutorBase<UseOnPosition>
{
public UseOnPositionExecutor(GameFunctions gameFunctions, QuestFunctions questFunctions, ICondition condition, ILogger<UseOnPosition> logger)
{
_003CgameFunctions_003EP = gameFunctions;
base._002Ector(questFunctions, condition, (ILogger)logger);
}
protected override bool UseItem()
{
return _003CgameFunctions_003EP.UseItemOnPosition(base.Task.Position, base.ItemId);
}
}
internal sealed record UseOnObject(ElementId? QuestId, uint DataId, uint ItemId, IList<QuestWorkValue?> CompletionQuestVariablesFlags, bool StartingCombat = false) : IUseItemBase, ITask
{
public override string ToString()
{
return $"UseItem({ItemId} on {DataId})";
}
}
internal sealed class UseOnObjectExecutor : UseItemExecutorBase<UseOnObject>
{
public UseOnObjectExecutor(QuestFunctions questFunctions, GameFunctions gameFunctions, ICondition condition, ILogger<UseOnObject> logger)
{
_003CgameFunctions_003EP = gameFunctions;
base._002Ector(questFunctions, condition, (ILogger)logger);
}
protected override bool UseItem()
{
return _003CgameFunctions_003EP.UseItem(base.Task.DataId, base.ItemId);
}
}
internal sealed record UseOnSelf(ElementId? QuestId, uint ItemId, IList<QuestWorkValue?> CompletionQuestVariablesFlags, bool StartingCombat = false) : IUseItemBase, ITask
{
public override string ToString()
{
return $"UseItem({ItemId})";
}
}
internal sealed class UseOnSelfExecutor : UseItemExecutorBase<UseOnSelf>
{
public UseOnSelfExecutor(GameFunctions gameFunctions, QuestFunctions questFunctions, ICondition condition, ILogger<UseOnSelf> logger)
{
_003CgameFunctions_003EP = gameFunctions;
base._002Ector(questFunctions, condition, (ILogger)logger);
}
protected override bool UseItem()
{
return _003CgameFunctions_003EP.UseItem(base.ItemId);
}
}
}