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 logger) : TaskExecutor() { private bool _wasGathering; private bool _usedLuck; private SlotInfo? _slotToGather; private Queue? _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("GatheringMasterpiece", out var _)) { return ETaskResult.TaskComplete; } _wasGathering = true; if (gameGui.TryGetAddonByName("Gathering", out var addonPtr2)) { if (gatheringController.HasRequestedItems()) { addonPtr2->FireCallbackInt(-1); } else { List 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 ReadSlots(AddonGathering* addonGathering) { List list = new List(); 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? GetNextActions(NodeCondition nodeCondition, List slots) { if (_slotToGather != null && slots.All((SlotInfo x) => x.Index != _slotToGather.Index)) { _slotToGather = null; } Queue queue = new Queue(); 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); }