283 lines
7.6 KiB
C#
283 lines
7.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Dalamud.Game.ClientState.Conditions;
|
|
using Dalamud.Plugin.Services;
|
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
using LLib.Gear;
|
|
using LLib.Inventory;
|
|
using Microsoft.Extensions.Logging;
|
|
using Questionable.Controller.Steps.Common;
|
|
using Questionable.Data;
|
|
using Questionable.Functions;
|
|
using Questionable.Model;
|
|
using Questionable.Model.Questing;
|
|
|
|
namespace Questionable.Controller.Steps.Shared;
|
|
|
|
internal static class RedeemRewardItems
|
|
{
|
|
internal sealed class Factory(QuestData questData, Configuration configuration, ILogger<Factory> logger) : ITaskFactory
|
|
{
|
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
|
{
|
|
if (step.InteractionType != EInteractionType.AcceptQuest)
|
|
{
|
|
return Array.Empty<ITask>();
|
|
}
|
|
return CreateTasks(questData, configuration, logger);
|
|
}
|
|
}
|
|
|
|
internal sealed class CompletionFactory : ITaskFactory
|
|
{
|
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
|
{
|
|
if (step.InteractionType != EInteractionType.CompleteQuest)
|
|
{
|
|
return Array.Empty<ITask>();
|
|
}
|
|
return new global::_003C_003Ez__ReadOnlySingleElementList<ITask>(new ScanAfterQuestCompletion(step.TurnInQuestId ?? quest.Id));
|
|
}
|
|
}
|
|
|
|
internal sealed record ScanAfterQuestCompletion(ElementId ElementId) : ITask
|
|
{
|
|
public override string ToString()
|
|
{
|
|
return $"ScanRewardsAfterQuestCompletion({ElementId})";
|
|
}
|
|
}
|
|
|
|
internal sealed class ScanAfterQuestCompletionExecutor(QuestFunctions questFunctions, QuestData questData, Configuration configuration, ILogger<ScanAfterQuestCompletionExecutor> logger) : TaskExecutor<ScanAfterQuestCompletion>(), IExtraTaskCreator, ITaskExecutor
|
|
{
|
|
private const long InventorySettleDelayMs = 1000L;
|
|
|
|
private long? _scanAt;
|
|
|
|
private List<ITask> _tasks = new List<ITask>();
|
|
|
|
protected override bool Start()
|
|
{
|
|
_scanAt = null;
|
|
_tasks = new List<ITask>();
|
|
return true;
|
|
}
|
|
|
|
public override ETaskResult Update()
|
|
{
|
|
if (!questFunctions.IsQuestComplete(base.Task.ElementId))
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
long tickCount = Environment.TickCount64;
|
|
if (!_scanAt.HasValue)
|
|
{
|
|
_scanAt = tickCount + 1000;
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
if (tickCount < _scanAt)
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
_tasks = CreateTasks(questData, configuration, logger);
|
|
if (_tasks.Count <= 0)
|
|
{
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
return ETaskResult.CreateNewTasks;
|
|
}
|
|
|
|
public IEnumerable<ITask> CreateExtraTasks()
|
|
{
|
|
return _tasks;
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal sealed record Task(ItemReward ItemReward) : ITask
|
|
{
|
|
public int OriginalGearsetIndex { get; set; } = -1;
|
|
|
|
public override string ToString()
|
|
{
|
|
return "TryRedeem(" + ItemReward.Name + ")";
|
|
}
|
|
}
|
|
|
|
internal sealed class Executor(GameFunctions gameFunctions, Configuration configuration, ICondition condition, ILogger<Executor> logger) : TaskExecutor<Task>(), IStoppableTaskExecutor, ITaskExecutor
|
|
{
|
|
private static readonly TimeSpan MinimumCastTime = TimeSpan.FromSeconds(4L);
|
|
|
|
private const long GearsetSettleDelayMs = 1000L;
|
|
|
|
private const long UseItemRetryDelayMs = 1000L;
|
|
|
|
private const int MaxUseItemAttempts = 5;
|
|
|
|
private long _useItemAt;
|
|
|
|
private long _continueAt;
|
|
|
|
private int _useItemAttempts;
|
|
|
|
private bool _itemUsed;
|
|
|
|
protected override bool Start()
|
|
{
|
|
if (condition[ConditionFlag.Mounted] && base.Task.OriginalGearsetIndex < 0)
|
|
{
|
|
return false;
|
|
}
|
|
long num = (_useItemAt = Environment.TickCount64);
|
|
if (base.Task.ItemReward is CofferReward)
|
|
{
|
|
int? cofferGearsetIndex = configuration.General.CofferGearsetIndex;
|
|
if (cofferGearsetIndex.HasValue)
|
|
{
|
|
int valueOrDefault = cofferGearsetIndex.GetValueOrDefault();
|
|
if (base.Task.OriginalGearsetIndex < 0)
|
|
{
|
|
int currentGearsetIndex = GearsetHelper.GetCurrentGearsetIndex();
|
|
if (currentGearsetIndex != valueOrDefault && GearsetHelper.TryEquipGearset(valueOrDefault))
|
|
{
|
|
base.Task.OriginalGearsetIndex = currentGearsetIndex;
|
|
_useItemAt = num + 1000;
|
|
}
|
|
}
|
|
else if (GearsetHelper.GetCurrentGearsetIndex() != valueOrDefault)
|
|
{
|
|
GearsetHelper.TryEquipGearset(valueOrDefault);
|
|
_useItemAt = num + 1000;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public override ETaskResult Update()
|
|
{
|
|
long tickCount = Environment.TickCount64;
|
|
if (!_itemUsed)
|
|
{
|
|
if (tickCount < _useItemAt)
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
if (base.Task.ItemReward.IsUnlocked() || !HasItemInInventory(base.Task.ItemReward.ItemId))
|
|
{
|
|
RestoreGearset();
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
if (gameFunctions.UseItem(base.Task.ItemReward.ItemId))
|
|
{
|
|
_itemUsed = true;
|
|
TimeSpan timeSpan = base.Task.ItemReward.CastTime;
|
|
if (timeSpan < MinimumCastTime)
|
|
{
|
|
timeSpan = MinimumCastTime;
|
|
}
|
|
_continueAt = tickCount + (long)timeSpan.TotalMilliseconds + 3000;
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
_useItemAttempts++;
|
|
if (_useItemAttempts >= 5)
|
|
{
|
|
logger.LogWarning("Unable to use {ItemName} after {Attempts} attempts, giving up", base.Task.ItemReward.Name, _useItemAttempts);
|
|
RestoreGearset();
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
_useItemAt = tickCount + 1000;
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
if (condition[ConditionFlag.Casting])
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
if (tickCount <= _continueAt)
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
RestoreGearset();
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
|
|
public void StopNow()
|
|
{
|
|
RestoreGearset();
|
|
}
|
|
|
|
private void RestoreGearset()
|
|
{
|
|
if (base.Task.OriginalGearsetIndex >= 0 && GearsetHelper.TryEquipGearset(base.Task.OriginalGearsetIndex))
|
|
{
|
|
base.Task.OriginalGearsetIndex = -1;
|
|
}
|
|
}
|
|
|
|
private unsafe static bool HasItemInInventory(uint itemId)
|
|
{
|
|
InventoryManager* ptr = InventoryManager.Instance();
|
|
if (ptr != null)
|
|
{
|
|
return ptr->GetInventoryItemCount(itemId, isHq: false, checkEquipped: true, checkArmory: true, 0) > 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private unsafe static List<ITask> CreateTasks(QuestData questData, Configuration configuration, ILogger logger)
|
|
{
|
|
List<ITask> list = new List<ITask>();
|
|
InventoryManager* ptr = InventoryManager.Instance();
|
|
if (ptr == null)
|
|
{
|
|
return list;
|
|
}
|
|
bool flag = InventoryHelper.HasFreeInventorySlot();
|
|
foreach (ItemReward redeemableItem in questData.RedeemableItems)
|
|
{
|
|
if (ptr->GetInventoryItemCount(redeemableItem.ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0) != 0 && !redeemableItem.IsUnlocked())
|
|
{
|
|
if (!PassesRedemptionFilters(redeemableItem.Type, redeemableItem.IsUntradable, configuration.General, flag))
|
|
{
|
|
logger.LogDebug("Skipping quest reward {ItemName}: type={RewardType}, untradable={IsUntradable}, freeInventorySlot={HasFreeInventorySlot}", redeemableItem.Name, redeemableItem.Type, redeemableItem.IsUntradable, flag);
|
|
}
|
|
else
|
|
{
|
|
list.Add(new Task(redeemableItem));
|
|
}
|
|
}
|
|
}
|
|
if (list.Count > 0)
|
|
{
|
|
list.Insert(0, new Mount.UnmountTask());
|
|
}
|
|
return list;
|
|
}
|
|
|
|
internal static bool PassesRedemptionFilters(EItemRewardType type, bool isUntradable, Configuration.GeneralConfiguration general, bool hasFreeInventorySlot)
|
|
{
|
|
if (general.DisabledRewardTypes.Contains(type))
|
|
{
|
|
return false;
|
|
}
|
|
if (general.SkipTradableRewards && !isUntradable)
|
|
{
|
|
return false;
|
|
}
|
|
if (type == EItemRewardType.Coffer && !hasFreeInventorySlot)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|