using System; using System.Collections.Generic; using System.Linq; using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using LLib.GameData; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Questionable.Controller.Steps.Common; using Questionable.Controller.Steps.Interactions; using Questionable.Data; using Questionable.Model; using Questionable.Model.Gathering; using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Shared; internal static class Gather { internal sealed class Factory : ITaskFactory { public IEnumerable CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { if (step.InteractionType != EInteractionType.Gather) { yield break; } foreach (GatheredItem item in step.ItemsToGather) { yield return new DelayedGatheringTask(item, quest, sequence.Sequence); } } } internal sealed record DelayedGatheringTask(GatheredItem GatheredItem, Quest Quest, byte Sequence) : ITask { public override string ToString() { return $"Gathering(pending for {GatheredItem.ItemId})"; } } internal sealed class DelayedGatheringExecutor(GatheringPointRegistry gatheringPointRegistry, TerritoryData territoryData, IClientState clientState, IServiceProvider serviceProvider, ILogger logger) : TaskExecutor(), IExtraTaskCreator, ITaskExecutor { protected override bool Start() { return true; } public override ETaskResult Update() { return ETaskResult.CreateNewTasks; } public IEnumerable CreateExtraTasks() { EClassJob rowId = (EClassJob)clientState.LocalPlayer.ClassJob.RowId; if (!gatheringPointRegistry.TryGetGatheringPointId(base.Task.GatheredItem.ItemId, rowId, out GatheringPointId gatheringPointId)) { throw new TaskException($"No gathering point found for item {base.Task.GatheredItem.ItemId}"); } if (!gatheringPointRegistry.TryGetGatheringPoint(gatheringPointId, out GatheringRoot gatheringRoot)) { throw new TaskException($"No path found for gathering point {gatheringPointId}"); } if (HasRequiredItems(base.Task.GatheredItem)) { yield break; } switch (rowId) { case EClassJob.Miner: yield return new Questionable.Controller.Steps.Interactions.Action.TriggerStatusIfMissing(EStatus.Prospect, EAction.Prospect); break; case EClassJob.Botanist: yield return new Questionable.Controller.Steps.Interactions.Action.TriggerStatusIfMissing(EStatus.Triangulate, EAction.Triangulate); break; } using (logger.BeginScope("Gathering(inner)")) { QuestSequence gatheringSequence = new QuestSequence { Sequence = 0, Steps = gatheringRoot.Steps }; foreach (QuestStep step in gatheringSequence.Steps) { foreach (ITask item in serviceProvider.GetRequiredService().CreateTasks(base.Task.Quest, base.Task.Sequence, gatheringSequence, step)) { if (item is WaitAtEnd.NextStep) { yield return new SkipMarker(); } else { yield return item; } } } } ushort territoryId = gatheringRoot.Steps.Last().TerritoryId; yield return new WaitCondition.Task(() => clientState.TerritoryType == territoryId, "Wait(territory: " + territoryData.GetNameAndId(territoryId) + ")"); yield return new WaitNavmesh.Task(); yield return new GatheringTask(gatheringPointId, base.Task.GatheredItem); yield return new WaitAtEnd.WaitDelay(); } private unsafe bool HasRequiredItems(GatheredItem itemToGather) { InventoryManager* ptr = InventoryManager.Instance(); if (ptr != null) { return ptr->GetInventoryItemCount(itemToGather.ItemId, isHq: false, checkEquipped: true, checkArmory: true, (short)itemToGather.Collectability) >= itemToGather.ItemCount; } return false; } public override bool ShouldInterruptOnDamage() { return false; } } internal sealed record GatheringTask(GatheringPointId gatheringPointId, GatheredItem gatheredItem) : ITask { public override string ToString() { if (gatheredItem.Collectability != 0) { return $"Gather({gatheredItem.ItemCount}x {gatheredItem.ItemId} {SeIconChar.Collectible.ToIconString()} {gatheredItem.Collectability})"; } return $"Gather({gatheredItem.ItemCount}x {gatheredItem.ItemId})"; } } internal sealed class StartGathering(GatheringController gatheringController) : TaskExecutor(), IToastAware, ITaskExecutor { protected override bool Start() { return gatheringController.Start(new GatheringController.GatheringRequest(base.Task.gatheringPointId, base.Task.gatheredItem.ItemId, base.Task.gatheredItem.AlternativeItemId, base.Task.gatheredItem.ItemCount, base.Task.gatheredItem.Collectability)); } public override ETaskResult Update() { if (gatheringController.Update() == GatheringController.EStatus.Complete) { return ETaskResult.TaskComplete; } return ETaskResult.StillRunning; } public bool OnErrorToast(SeString message) { bool isHandled = false; gatheringController.OnErrorToast(ref message, ref isHandled); return isHandled; } public override bool ShouldInterruptOnDamage() { return false; } } internal sealed class SkipMarker : ITask { public override string ToString() { return "Gather/SkipMarker"; } } internal sealed class DoSkip : TaskExecutor { protected override bool Start() { return true; } public override ETaskResult Update() { return ETaskResult.TaskComplete; } public override bool ShouldInterruptOnDamage() { return false; } } }