using System; using System.Collections.Generic; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.Group; using LLib.Gear; using Microsoft.Extensions.Logging; using Questionable.Controller.Steps.Common; using Questionable.Controller.Steps.Shared; using Questionable.Controller.Utils; using Questionable.Data; using Questionable.External; using Questionable.Functions; using Questionable.Model; using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Interactions; internal static class Duty { internal sealed class Factory(AutoDutyIpc autoDutyIpc, Configuration configuration) : ITaskFactory { public IEnumerable CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { if (step.InteractionType != EInteractionType.Duty) { yield break; } ArgumentNullException.ThrowIfNull(step.DutyOptions, "step.DutyOptions"); if (autoDutyIpc.IsConfiguredToRunContent(step.DutyOptions)) { AutoDutyIpc.DutyMode dutyMode = GetDutyMode(step.DutyOptions.ContentFinderConditionId, step.DutyOptions.DutyMode); if (dutyMode == AutoDutyIpc.DutyMode.UnsyncRegular && (step.DutyOptions.DutyMode == EDutyMode.UnsyncParty || (!step.DutyOptions.DutyMode.HasValue && configuration.Duties.DutyModeOverrides.TryGetValue(step.DutyOptions.ContentFinderConditionId, out var value) && value == EDutyMode.UnsyncParty) || (!step.DutyOptions.DutyMode.HasValue && !configuration.Duties.DutyModeOverrides.ContainsKey(step.DutyOptions.ContentFinderConditionId) && configuration.Duties.DefaultDutyMode == EDutyMode.UnsyncParty))) { yield return new WaitForPartyTask(); } yield return new StartAutoDutyTask(step.DutyOptions.ContentFinderConditionId, dutyMode); yield return new WaitAutoDutyTask(step.DutyOptions.ContentFinderConditionId); if (!QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags)) { yield return new WaitAtEnd.WaitNextStepOrSequence(); } } else if (!step.DutyOptions.LowPriority) { yield return new OpenDutyFinderTask(step.DutyOptions.ContentFinderConditionId); } } private AutoDutyIpc.DutyMode GetDutyMode(uint cfcId, EDutyMode? stepDutyMode) { if (stepDutyMode.HasValue) { return ConvertToAutoDutyMode(stepDutyMode.Value); } if (configuration.Duties.DutyModeOverrides.TryGetValue(cfcId, out var value)) { return ConvertToAutoDutyMode(value); } return ConvertToAutoDutyMode(configuration.Duties.DefaultDutyMode); } private static AutoDutyIpc.DutyMode ConvertToAutoDutyMode(EDutyMode mode) { return mode switch { EDutyMode.Support => AutoDutyIpc.DutyMode.Support, EDutyMode.UnsyncSolo => AutoDutyIpc.DutyMode.UnsyncRegular, EDutyMode.UnsyncParty => AutoDutyIpc.DutyMode.UnsyncRegular, _ => AutoDutyIpc.DutyMode.Support, }; } } internal sealed record WaitForPartyTask : ITask { public override string ToString() { return "WaitForParty"; } } internal sealed class WaitForPartyExecutor(IChatGui chatGui, ILogger logger) : TaskExecutor() { private DateTime _lastWarningTime = DateTime.MinValue; protected override bool Start() { logger.LogInformation("Waiting for party members before starting duty..."); return true; } public unsafe override ETaskResult Update() { GroupManager* ptr = GroupManager.Instance(); if (ptr == null) { return ETaskResult.StillRunning; } byte memberCount = ptr->MainGroup.MemberCount; bool isAlliance = ptr->MainGroup.IsAlliance; if (memberCount > 1 || isAlliance) { logger.LogInformation("Party detected with {MemberCount} members, proceeding with duty", memberCount); return ETaskResult.TaskComplete; } if (DateTime.Now - _lastWarningTime > TimeSpan.FromSeconds(10L)) { chatGui.Print("[Questionable] Waiting for party members before starting duty (Unsync Party mode)...", "Questionable", 576); _lastWarningTime = DateTime.Now; } return ETaskResult.StillRunning; } public override bool ShouldInterruptOnDamage() { return false; } } internal sealed record StartAutoDutyTask(uint ContentFinderConditionId, AutoDutyIpc.DutyMode DutyMode) : ITask { public override string ToString() { return $"StartAutoDuty({ContentFinderConditionId}, {DutyMode})"; } } internal sealed class StartAutoDutyExecutor(GearStatsCalculator gearStatsCalculator, AutoDutyIpc autoDutyIpc, TerritoryData territoryData, IClientState clientState, IChatGui chatGui, SendNotification.Executor sendNotificationExecutor) : TaskExecutor(), IStoppableTaskExecutor, ITaskExecutor { protected unsafe override bool Start() { if (!territoryData.TryGetContentFinderCondition(base.Task.ContentFinderConditionId, out TerritoryData.ContentFinderConditionData contentFinderConditionData)) { throw new TaskException("Failed to get territory ID for content finder condition"); } InventoryManager* intPtr = InventoryManager.Instance(); if (intPtr == null) { throw new TaskException("Inventory unavailable"); } InventoryContainer* inventoryContainer = intPtr->GetInventoryContainer(InventoryType.EquippedItems); if (inventoryContainer == null) { throw new TaskException("Equipped items unavailable"); } short num = gearStatsCalculator.CalculateAverageItemLevel(inventoryContainer); if (contentFinderConditionData.RequiredItemLevel > num) { string text = $"Could not use AutoDuty to queue for {contentFinderConditionData.Name}, required item level: {contentFinderConditionData.RequiredItemLevel}, current item level: {num}."; if (!sendNotificationExecutor.Start(new SendNotification.Task(EInteractionType.Duty, text))) { chatGui.PrintError(text, "Questionable", 576); } return false; } autoDutyIpc.StartInstance(base.Task.ContentFinderConditionId, base.Task.DutyMode); return true; } public override ETaskResult Update() { if (!territoryData.TryGetContentFinderCondition(base.Task.ContentFinderConditionId, out TerritoryData.ContentFinderConditionData contentFinderConditionData)) { throw new TaskException("Failed to get territory ID for content finder condition"); } if (clientState.TerritoryType != contentFinderConditionData.TerritoryId) { return ETaskResult.StillRunning; } return ETaskResult.TaskComplete; } public void StopNow() { autoDutyIpc.Stop(); } public override bool ShouldInterruptOnDamage() { return false; } } internal sealed record WaitAutoDutyTask(uint ContentFinderConditionId) : ITask { public override string ToString() { return $"Wait(AutoDuty, left instance {ContentFinderConditionId})"; } } internal sealed class WaitAutoDutyExecutor(AutoDutyIpc autoDutyIpc, TerritoryData territoryData, IClientState clientState) : TaskExecutor(), IStoppableTaskExecutor, ITaskExecutor { protected override bool Start() { return true; } public override ETaskResult Update() { if (!territoryData.TryGetContentFinderCondition(base.Task.ContentFinderConditionId, out TerritoryData.ContentFinderConditionData contentFinderConditionData)) { throw new TaskException("Failed to get territory ID for content finder condition"); } if (clientState.TerritoryType == contentFinderConditionData.TerritoryId || !autoDutyIpc.IsStopped()) { return ETaskResult.StillRunning; } return ETaskResult.TaskComplete; } public void StopNow() { autoDutyIpc.Stop(); } public override bool ShouldInterruptOnDamage() { return false; } } internal sealed record OpenDutyFinderTask(uint ContentFinderConditionId) : ITask { public override string ToString() { return $"OpenDutyFinder({ContentFinderConditionId})"; } } internal sealed class OpenDutyFinderExecutor(GameFunctions gameFunctions, ICondition condition) : TaskExecutor() { protected override bool Start() { if (condition[ConditionFlag.InDutyQueue]) { return false; } gameFunctions.OpenDutyFinder(base.Task.ContentFinderConditionId); return true; } public override ETaskResult Update() { return ETaskResult.TaskComplete; } public override bool ShouldInterruptOnDamage() { return false; } } }