using System; using System.Collections.Generic; using System.Linq; using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.Text; using Dalamud.Plugin.Services; 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 DoGatherCollectable { 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 $"DoGatherCollectable({SeIconChar.Collectible.ToIconString()}/{Request.Collectability}){(RevisitRequired ? " if revist" : "")}"; } } internal sealed class GatherCollectableExecutor(GatheringController gatheringController, GameFunctions gameFunctions, IObjectTable objectTable, IGameGui gameGui, ICondition condition, ILogger logger) : TaskExecutor() { private Queue? _actionQueue; private bool? _expectedScrutiny; private bool _wasGathering; private long _nextRevisitInteractionAt; protected override bool Start() { return true; } public unsafe override ETaskResult Update() { if (base.Task.RevisitRequired && !base.Task.RevisitTriggered) { logger.LogDebug("No revisit"); return ETaskResult.TaskComplete; } Task task = base.Task; if ((object)task != null && task.RevisitRequired && task.RevisitTriggered && !condition[ConditionFlag.Gathering]) { if (_wasGathering) { return ETaskResult.TaskComplete; } if (Environment.TickCount64 >= _nextRevisitInteractionAt) { IGameObject gameObject = objectTable.FirstOrDefault((IGameObject x) => x.BaseId == base.Task.Node.DataId && x.IsTargetable); if (gameObject != null && gameFunctions.InteractWith(gameObject)) { logger.LogDebug("Revisit triggered, re-interacting with node {DataId}", base.Task.Node.DataId); } _nextRevisitInteractionAt = Environment.TickCount64 + 1000; } return ETaskResult.StillRunning; } if (!base.Task.RevisitTriggered && gatheringController.HasNodeDisappeared(base.Task.Node)) { logger.LogDebug("Node disappeared"); return ETaskResult.TaskComplete; } if (condition[ConditionFlag.Gathering]) { _wasGathering = true; } if (gatheringController.HasRequestedItems()) { if (gameGui.TryGetAddonByName("GatheringMasterpiece", out var addonPtr)) { uint uInt = addonPtr->AtkValues[62].UInt; uint uInt2 = addonPtr->AtkValues[63].UInt; logger.LogDebug("Requested collectables complete with node integrity {Current}/{Max}", uInt, uInt2); if (uInt != 0) { addonPtr->FireCallbackInt(1); } return ETaskResult.StillRunning; } if (gameGui.TryGetAddonByName("Gathering", out addonPtr)) { uint uInt3 = addonPtr->AtkValues[109].UInt; uint uInt4 = addonPtr->AtkValues[110].UInt; logger.LogDebug("Leaving gathering interface with node integrity {Current}/{Max}", uInt3, uInt4); if (uInt3 != 0) { addonPtr->FireCallbackInt(-1); } return ETaskResult.StillRunning; } if (!condition[ConditionFlag.Gathering]) { return ETaskResult.TaskComplete; } return ETaskResult.StillRunning; } if (gameFunctions.GetFreeInventorySlots() == 0) { throw new TaskException("Inventory full"); } NodeCondition nodeCondition = GetNodeCondition(); if (nodeCondition == null) { task = base.Task; if ((object)task == null || !task.RevisitRequired || !task.RevisitTriggered) { return ETaskResult.TaskComplete; } return ETaskResult.StillRunning; } if (_expectedScrutiny.HasValue) { if (nodeCondition.ScrutinyActive != _expectedScrutiny) { return ETaskResult.StillRunning; } _expectedScrutiny = null; return ETaskResult.StillRunning; } if (_actionQueue != null && _actionQueue.TryPeek(out var result)) { if (gameFunctions.UseAction(result)) { bool? expectedScrutiny; switch (result) { case EAction.ScrutinyMiner: case EAction.ScrutinyBotanist: expectedScrutiny = true; break; case EAction.ScourMiner: case EAction.MeticulousMiner: case EAction.ScourBotanist: case EAction.MeticulousBotanist: expectedScrutiny = false; break; default: expectedScrutiny = null; break; } _expectedScrutiny = expectedScrutiny; logger.LogDebug("Used action {Action} on node", result); _actionQueue.Dequeue(); } return ETaskResult.StillRunning; } if (nodeCondition.CollectabilityToGoal(base.Task.Request.Collectability) != 0) { _actionQueue = GetNextActions(nodeCondition); if (_actionQueue != null) { foreach (EAction item in _actionQueue) { logger.LogDebug("Next Actions {Action}", item); } return ETaskResult.StillRunning; } } _actionQueue = new Queue(); _actionQueue.Enqueue(PickAction(EAction.CollectMiner, EAction.CollectBotanist)); return ETaskResult.StillRunning; } private unsafe NodeCondition? GetNodeCondition() { if (gameGui.TryGetAddonByName("GatheringMasterpiece", out var addonPtr)) { AtkValue* atkValues = addonPtr->AtkValues; return new NodeCondition(atkValues[13].UInt, atkValues[14].UInt, atkValues[62].UInt, atkValues[63].UInt, atkValues[54].Bool, atkValues[48].UInt, atkValues[51].UInt); } return null; } private Queue GetNextActions(NodeCondition nodeCondition) { uint num = objectTable.LocalPlayer?.CurrentGp ?? 0; logger.LogTrace("Getting next actions (with {GP} GP, {MeticulousCollectability}~ meticulous, {ScourCollectability}~ scour)", num, nodeCondition.CollectabilityFromMeticulous, nodeCondition.CollectabilityFromScour); Queue queue = new Queue(); uint num2 = nodeCondition.CollectabilityToGoal(base.Task.Request.Collectability); if (num2 <= nodeCondition.CollectabilityFromMeticulous) { logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ meticulous", num2, nodeCondition.CollectabilityFromMeticulous); queue.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist)); return queue; } if (num2 <= nodeCondition.CollectabilityFromScour) { logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ scour", num2, nodeCondition.CollectabilityFromScour); queue.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist)); return queue; } if (!nodeCondition.ScrutinyActive && num >= 200) { logger.LogTrace("Still missing {NeededCollectability} collectability, scrutiny inactive", num2); queue.Enqueue(PickAction(EAction.ScrutinyMiner, EAction.ScrutinyBotanist)); return queue; } if (nodeCondition.ScrutinyActive) { logger.LogTrace("Scrutiny active, need {NeededCollectability} and we expect {Collectability}~ meticulous", num2, nodeCondition.CollectabilityFromMeticulous); queue.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist)); return queue; } logger.LogTrace("Scrutiny inactive, need {NeededCollectability} and we expect {Collectability}~ scour", num2, nodeCondition.CollectabilityFromScour); queue.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist)); return queue; } private EAction PickAction(EAction minerAction, EAction botanistAction) { if (objectTable.LocalPlayer?.ClassJob.RowId == 16) { return minerAction; } return botanistAction; } public override bool ShouldInterruptOnDamage() { return false; } } private sealed record NodeCondition(uint CurrentCollectability, uint MaxCollectability, uint CurrentIntegrity, uint MaxIntegrity, bool ScrutinyActive, uint CollectabilityFromScour, uint CollectabilityFromMeticulous) { public uint CollectabilityToGoal(uint goal) { if (goal >= CurrentCollectability) { return goal - CurrentCollectability; } return (CurrentCollectability == 0) ? 1u : 0u; } } }