293 lines
9 KiB
C#
293 lines
9 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Dalamud.Game.ClientState.Conditions;
|
|
using Dalamud.Plugin.Services;
|
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
using LLib.GameUI;
|
|
using Microsoft.Extensions.Logging;
|
|
using Questionable.Functions;
|
|
using Questionable.Model.Gathering;
|
|
using Questionable.Model.Questing;
|
|
|
|
namespace Questionable.Controller.Steps.Gathering;
|
|
|
|
internal static class DoGather
|
|
{
|
|
internal sealed record Task(GatheringController.GatheringRequest Request, GatheringNode Node, bool RevisitRequired) : ITask, IRevisitAware
|
|
{
|
|
public bool RevisitTriggered { get; private set; }
|
|
|
|
public void OnRevisit()
|
|
{
|
|
RevisitTriggered = true;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "DoGather" + (RevisitRequired ? " if revist" : "");
|
|
}
|
|
}
|
|
|
|
internal sealed class GatherExecutor(GatheringController gatheringController, GameFunctions gameFunctions, IGameGui gameGui, IClientState clientState, ICondition condition, ILogger<GatherExecutor> logger) : TaskExecutor<Task>()
|
|
{
|
|
private bool _wasGathering;
|
|
|
|
private bool _usedLuck;
|
|
|
|
private SlotInfo? _slotToGather;
|
|
|
|
private Queue<EAction>? _actionQueue;
|
|
|
|
protected override bool Start()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public unsafe override ETaskResult Update()
|
|
{
|
|
Task task = base.Task;
|
|
if ((object)task != null && task.RevisitRequired && !task.RevisitTriggered)
|
|
{
|
|
logger.LogInformation("No revisit");
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
if (gatheringController.HasNodeDisappeared(base.Task.Node))
|
|
{
|
|
logger.LogInformation("Node disappeared");
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
if (gameFunctions.GetFreeInventorySlots() == 0)
|
|
{
|
|
throw new TaskException("Inventory full");
|
|
}
|
|
if (condition[ConditionFlag.Gathering])
|
|
{
|
|
if (gameGui.TryGetAddonByName<AtkUnitBase>("GatheringMasterpiece", out var _))
|
|
{
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
_wasGathering = true;
|
|
if (gameGui.TryGetAddonByName<AddonGathering>("Gathering", out var addonPtr2))
|
|
{
|
|
if (gatheringController.HasRequestedItems())
|
|
{
|
|
addonPtr2->FireCallbackInt(-1);
|
|
}
|
|
else
|
|
{
|
|
List<SlotInfo> list = ReadSlots(addonPtr2);
|
|
if (base.Task.Request.Collectability > 0)
|
|
{
|
|
SlotInfo slotInfo = list.Single((SlotInfo x) => x.ItemId == base.Task.Request.ItemId);
|
|
addonPtr2->FireCallbackInt(slotInfo.Index);
|
|
}
|
|
else
|
|
{
|
|
NodeCondition nodeCondition = new NodeCondition(addonPtr2->AtkValues[110].UInt, addonPtr2->AtkValues[111].UInt);
|
|
if (_actionQueue != null && _actionQueue.TryPeek(out var result))
|
|
{
|
|
if (gameFunctions.UseAction(result))
|
|
{
|
|
logger.LogInformation("Used action {Action} on node", result);
|
|
_actionQueue.Dequeue();
|
|
}
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
_actionQueue = GetNextActions(nodeCondition, list);
|
|
if (_actionQueue == null)
|
|
{
|
|
logger.LogInformation("Skipping the rest of gathering...");
|
|
addonPtr2->FireCallbackInt(-1);
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
if (_actionQueue.Count == 0)
|
|
{
|
|
SlotInfo slotInfo2 = _slotToGather ?? list.SingleOrDefault((SlotInfo x) => x.ItemId == base.Task.Request.ItemId) ?? list.MinBy((SlotInfo x) => x.ItemId);
|
|
switch (slotInfo2?.ItemId)
|
|
{
|
|
case 2u:
|
|
case 3u:
|
|
case 4u:
|
|
case 5u:
|
|
case 6u:
|
|
case 7u:
|
|
case 8u:
|
|
case 9u:
|
|
case 10u:
|
|
case 11u:
|
|
case 12u:
|
|
case 13u:
|
|
case 14u:
|
|
case 15u:
|
|
case 16u:
|
|
case 17u:
|
|
case 18u:
|
|
case 19u:
|
|
if (InventoryManager.Instance()->GetInventoryItemCount(slotInfo2.ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0) == 9999)
|
|
{
|
|
slotInfo2 = null;
|
|
}
|
|
break;
|
|
}
|
|
if (slotInfo2 != null)
|
|
{
|
|
addonPtr2->FireCallbackInt(slotInfo2.Index);
|
|
}
|
|
else
|
|
{
|
|
addonPtr2->FireCallbackInt(-1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!_wasGathering || condition[ConditionFlag.Gathering])
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
|
|
private unsafe List<SlotInfo> ReadSlots(AddonGathering* addonGathering)
|
|
{
|
|
List<SlotInfo> list = new List<SlotInfo>();
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
uint num = addonGathering->ItemIds[i];
|
|
if (num != 0)
|
|
{
|
|
AtkComponentCheckBox* value = addonGathering->GatheredItemComponentCheckbox[i].Value;
|
|
if (!int.TryParse(value->UldManager.SearchNodeById(10u)->GetAsAtkTextNode()->NodeText.ToString(), out var result))
|
|
{
|
|
result = 0;
|
|
}
|
|
if (!int.TryParse(value->UldManager.SearchNodeById(16u)->GetAsAtkTextNode()->NodeText.ToString(), out var result2))
|
|
{
|
|
result2 = 0;
|
|
}
|
|
AtkTextNode* asAtkTextNode = value->UldManager.SearchNodeById(31u)->GetAsAtkComponentNode()->Component->UldManager.SearchNodeById(7u)->GetAsAtkTextNode();
|
|
if (!asAtkTextNode->IsVisible() || !int.TryParse(asAtkTextNode->NodeText.ToString(), out var result3))
|
|
{
|
|
result3 = 1;
|
|
}
|
|
SlotInfo item = new SlotInfo(i, num, result, result2, result3);
|
|
list.Add(item);
|
|
}
|
|
}
|
|
logger.LogTrace("Slots: {Slots}", string.Join(", ", list));
|
|
return list;
|
|
}
|
|
|
|
private Queue<EAction>? GetNextActions(NodeCondition nodeCondition, List<SlotInfo> slots)
|
|
{
|
|
if (_slotToGather != null && slots.All((SlotInfo x) => x.Index != _slotToGather.Index))
|
|
{
|
|
_slotToGather = null;
|
|
}
|
|
Queue<EAction> queue = new Queue<EAction>();
|
|
if (!gameFunctions.HasStatus(EStatus.GatheringRateUp))
|
|
{
|
|
if (base.Task.Request.AlternativeItemId != 0)
|
|
{
|
|
SlotInfo slotInfo = slots.Single((SlotInfo x) => x.ItemId == base.Task.Request.AlternativeItemId);
|
|
if (slotInfo.GatheringChance == 100)
|
|
{
|
|
_slotToGather = slotInfo;
|
|
return queue;
|
|
}
|
|
if (slotInfo.GatheringChance > 0)
|
|
{
|
|
if (slotInfo.GatheringChance >= 95 && CanUseAction(EAction.SharpVision1, EAction.FieldMastery1))
|
|
{
|
|
_slotToGather = slotInfo;
|
|
queue.Enqueue(PickAction(EAction.SharpVision1, EAction.FieldMastery1));
|
|
return queue;
|
|
}
|
|
if (slotInfo.GatheringChance >= 85 && CanUseAction(EAction.SharpVision2, EAction.FieldMastery2))
|
|
{
|
|
_slotToGather = slotInfo;
|
|
queue.Enqueue(PickAction(EAction.SharpVision2, EAction.FieldMastery2));
|
|
return queue;
|
|
}
|
|
if (slotInfo.GatheringChance >= 50 && CanUseAction(EAction.SharpVision3, EAction.FieldMastery3))
|
|
{
|
|
_slotToGather = slotInfo;
|
|
queue.Enqueue(PickAction(EAction.SharpVision3, EAction.FieldMastery3));
|
|
return queue;
|
|
}
|
|
}
|
|
}
|
|
SlotInfo slotInfo2 = slots.SingleOrDefault((SlotInfo x) => x.ItemId == base.Task.Request.ItemId);
|
|
if (slotInfo2 == null)
|
|
{
|
|
if (!_usedLuck && nodeCondition.CurrentIntegrity == nodeCondition.MaxIntegrity && CanUseAction(EAction.LuckOfTheMountaineer, EAction.LuckOfThePioneer))
|
|
{
|
|
_usedLuck = true;
|
|
queue.Enqueue(PickAction(EAction.LuckOfTheMountaineer, EAction.LuckOfThePioneer));
|
|
return queue;
|
|
}
|
|
if (_usedLuck)
|
|
{
|
|
if (nodeCondition.CurrentIntegrity != nodeCondition.MaxIntegrity)
|
|
{
|
|
return null;
|
|
}
|
|
_slotToGather = slots.MinBy((SlotInfo x) => x.ItemId);
|
|
return queue;
|
|
}
|
|
}
|
|
slotInfo2 = slots.SingleOrDefault((SlotInfo x) => x.ItemId == base.Task.Request.ItemId);
|
|
if ((object)slotInfo2 != null)
|
|
{
|
|
int gatheringChance = slotInfo2.GatheringChance;
|
|
if (gatheringChance > 0 && gatheringChance < 100)
|
|
{
|
|
if (slotInfo2.GatheringChance >= 95 && CanUseAction(EAction.SharpVision1, EAction.FieldMastery1))
|
|
{
|
|
queue.Enqueue(PickAction(EAction.SharpVision1, EAction.FieldMastery1));
|
|
return queue;
|
|
}
|
|
if (slotInfo2.GatheringChance >= 85 && CanUseAction(EAction.SharpVision2, EAction.FieldMastery2))
|
|
{
|
|
queue.Enqueue(PickAction(EAction.SharpVision2, EAction.FieldMastery2));
|
|
return queue;
|
|
}
|
|
if (slotInfo2.GatheringChance >= 50 && CanUseAction(EAction.SharpVision3, EAction.FieldMastery3))
|
|
{
|
|
queue.Enqueue(PickAction(EAction.SharpVision3, EAction.FieldMastery3));
|
|
return queue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return queue;
|
|
}
|
|
|
|
private EAction PickAction(EAction minerAction, EAction botanistAction)
|
|
{
|
|
if (clientState.LocalPlayer?.ClassJob.RowId == 16)
|
|
{
|
|
return minerAction;
|
|
}
|
|
return botanistAction;
|
|
}
|
|
|
|
private unsafe bool CanUseAction(EAction minerAction, EAction botanistAction)
|
|
{
|
|
EAction actionId = PickAction(minerAction, botanistAction);
|
|
return ActionManager.Instance()->GetActionStatus(ActionType.Action, (uint)actionId, 3758096384uL, checkRecastActive: true, checkCastingActive: true, null) == 0;
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private sealed record SlotInfo(int Index, uint ItemId, int GatheringChance, int BoonChance, int Quantity);
|
|
|
|
private sealed record NodeCondition(uint CurrentIntegrity, uint MaxIntegrity);
|
|
}
|