using System.Collections.Generic; 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, IClientState clientState, IGameGui gameGui, ILogger logger) : TaskExecutor() { private Queue? _actionQueue; private bool? _expectedScrutiny; protected override bool Start() { return true; } public unsafe override ETaskResult Update() { if (base.Task.RevisitRequired && !base.Task.RevisitTriggered) { logger.LogInformation("No revisit"); return ETaskResult.TaskComplete; } if (gatheringController.HasNodeDisappeared(base.Task.Node)) { logger.LogInformation("Node disappeared"); return ETaskResult.TaskComplete; } if (gatheringController.HasRequestedItems()) { if (gameGui.TryGetAddonByName("GatheringMasterpiece", out var addonPtr)) { addonPtr->FireCallbackInt(1); return ETaskResult.StillRunning; } if (gameGui.TryGetAddonByName("Gathering", out addonPtr)) { addonPtr->FireCallbackInt(-1); return ETaskResult.TaskComplete; } } if (gameFunctions.GetFreeInventorySlots() == 0) { throw new TaskException("Inventory full"); } NodeCondition nodeCondition = GetNodeCondition(); if (nodeCondition == null) { return ETaskResult.TaskComplete; } 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.LogInformation("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.LogInformation("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 currentGp = clientState.LocalPlayer.CurrentGp; logger.LogTrace("Getting next actions (with {GP} GP, {MeticulousCollectability}~ meticulous, {ScourCollectability}~ scour)", currentGp, nodeCondition.CollectabilityFromMeticulous, nodeCondition.CollectabilityFromScour); Queue queue = new Queue(); uint num = nodeCondition.CollectabilityToGoal(base.Task.Request.Collectability); if (num <= nodeCondition.CollectabilityFromMeticulous) { logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ meticulous", num, nodeCondition.CollectabilityFromMeticulous); queue.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist)); return queue; } if (num <= nodeCondition.CollectabilityFromScour) { logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ scour", num, nodeCondition.CollectabilityFromScour); queue.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist)); return queue; } if (!nodeCondition.ScrutinyActive && currentGp >= 200) { logger.LogTrace("Still missing {NeededCollectability} collectability, scrutiny inactive", num); queue.Enqueue(PickAction(EAction.ScrutinyMiner, EAction.ScrutinyBotanist)); return queue; } if (nodeCondition.ScrutinyActive) { logger.LogTrace("Scrutiny active, need {NeededCollectability} and we expect {Collectability}~ meticulous", num, nodeCondition.CollectabilityFromMeticulous); queue.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist)); return queue; } logger.LogTrace("Scrutiny active, need {NeededCollectability} and we expect {Collectability}~ scour", num, nodeCondition.CollectabilityFromScour); queue.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist)); return queue; } private EAction PickAction(EAction minerAction, EAction botanistAction) { if (clientState.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; } } }