293 lines
10 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|